From 632740af3c73f68e6ee6ddb0a0e6103265cbc228 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Mon, 3 Apr 2023 11:06:20 +0200 Subject: [PATCH 001/160] init Umfrage Plugin --- .../org.projectforge.plugins.umfrage/pom.xml | 57 +++++++++++++++++++ .../plugins/umfrage/UmfragePageRest.kt | 4 ++ 2 files changed, 61 insertions(+) create mode 100644 plugins/org.projectforge.plugins.umfrage/pom.xml create mode 100644 plugins/org.projectforge.plugins.umfrage/src/main/kotlin/org/projectforge/plugins/umfrage/UmfragePageRest.kt diff --git a/plugins/org.projectforge.plugins.umfrage/pom.xml b/plugins/org.projectforge.plugins.umfrage/pom.xml new file mode 100644 index 0000000000..d99b514ba5 --- /dev/null +++ b/plugins/org.projectforge.plugins.umfrage/pom.xml @@ -0,0 +1,57 @@ + + + + projectforge-parent + org.projectforge + 7.5.1-SNAPSHOT + ../../pom.xml + + 4.0.0 + + org.projectforge.plugins.umfrage + org.projectforge.plugins.umfrage + + + 11 + 11 + UTF-8 + 1.8.20 + + + + + org.jetbrains.kotlin + kotlin-stdlib-js + ${kotlin.version} + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + js + + + + test-compile + test-compile + + test-js + + + + + + + + \ No newline at end of file diff --git a/plugins/org.projectforge.plugins.umfrage/src/main/kotlin/org/projectforge/plugins/umfrage/UmfragePageRest.kt b/plugins/org.projectforge.plugins.umfrage/src/main/kotlin/org/projectforge/plugins/umfrage/UmfragePageRest.kt new file mode 100644 index 0000000000..fd813e2033 --- /dev/null +++ b/plugins/org.projectforge.plugins.umfrage/src/main/kotlin/org/projectforge/plugins/umfrage/UmfragePageRest.kt @@ -0,0 +1,4 @@ +package org.projectforge.plugins.umfrage + +class UmfragePageRest { +} \ No newline at end of file From ba0c2da55b9a9af70b46845588db0f438598b424 Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Mon, 3 Apr 2023 13:04:01 +0200 Subject: [PATCH 002/160] create rest page with example and delete plugin --- .../org.projectforge.plugins.umfrage/pom.xml | 57 ------------------- .../plugins/umfrage/UmfragePageRest.kt | 4 -- .../projectforge/rest/umfrage/UmfrageData.kt | 5 ++ .../rest/umfrage/UmfragePageRest.kt | 57 +++++++++++++++++++ 4 files changed, 62 insertions(+), 61 deletions(-) delete mode 100644 plugins/org.projectforge.plugins.umfrage/pom.xml delete mode 100644 plugins/org.projectforge.plugins.umfrage/src/main/kotlin/org/projectforge/plugins/umfrage/UmfragePageRest.kt create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/umfrage/UmfrageData.kt create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/umfrage/UmfragePageRest.kt diff --git a/plugins/org.projectforge.plugins.umfrage/pom.xml b/plugins/org.projectforge.plugins.umfrage/pom.xml deleted file mode 100644 index d99b514ba5..0000000000 --- a/plugins/org.projectforge.plugins.umfrage/pom.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - projectforge-parent - org.projectforge - 7.5.1-SNAPSHOT - ../../pom.xml - - 4.0.0 - - org.projectforge.plugins.umfrage - org.projectforge.plugins.umfrage - - - 11 - 11 - UTF-8 - 1.8.20 - - - - - org.jetbrains.kotlin - kotlin-stdlib-js - ${kotlin.version} - - - - - - - org.jetbrains.kotlin - kotlin-maven-plugin - ${kotlin.version} - - - compile - compile - - js - - - - test-compile - test-compile - - test-js - - - - - - - - \ No newline at end of file diff --git a/plugins/org.projectforge.plugins.umfrage/src/main/kotlin/org/projectforge/plugins/umfrage/UmfragePageRest.kt b/plugins/org.projectforge.plugins.umfrage/src/main/kotlin/org/projectforge/plugins/umfrage/UmfragePageRest.kt deleted file mode 100644 index fd813e2033..0000000000 --- a/plugins/org.projectforge.plugins.umfrage/src/main/kotlin/org/projectforge/plugins/umfrage/UmfragePageRest.kt +++ /dev/null @@ -1,4 +0,0 @@ -package org.projectforge.plugins.umfrage - -class UmfragePageRest { -} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/umfrage/UmfrageData.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/umfrage/UmfrageData.kt new file mode 100644 index 0000000000..a1b9b11bae --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/umfrage/UmfrageData.kt @@ -0,0 +1,5 @@ +package org.projectforge.rest.umfrage + +class UmfrageData { + var month: Int = 0 +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/umfrage/UmfragePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/umfrage/UmfragePageRest.kt new file mode 100644 index 0000000000..317b90fc25 --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/umfrage/UmfragePageRest.kt @@ -0,0 +1,57 @@ +package org.projectforge.rest.umfrage + +import org.projectforge.business.scripting.I18n +import org.projectforge.rest.config.Rest +import org.projectforge.rest.core.AbstractDynamicPageRest +import org.projectforge.rest.core.RestResolver +import org.projectforge.rest.dto.FormLayoutData +import org.projectforge.ui.* +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import javax.servlet.http.HttpServletRequest + +@RestController +@RequestMapping("${Rest.URL}/umfrage") +class UmfragePageRest : AbstractDynamicPageRest() { + + val months = arrayOf( + I18n.getString("calendar.month.january"), + I18n.getString("calendar.month.february"), + I18n.getString("calendar.month.march"), + I18n.getString("calendar.month.april"), + I18n.getString("calendar.month.may"), + I18n.getString("calendar.month.june"), + I18n.getString("calendar.month.july"), + I18n.getString("calendar.month.august"), + I18n.getString("calendar.month.september"), + I18n.getString("calendar.month.october"), + I18n.getString("calendar.month.november"), + I18n.getString("calendar.month.december") + ) + + @GetMapping("dynamic") + fun getForm(request: HttpServletRequest): FormLayoutData { + val layout = UILayout(I18n.getString("menu.birthdayList")) + + val values = ArrayList>() + months.forEachIndexed { index, month -> values.add(UISelectValue(index + 1, month)) } + + layout.add(UISelect("month", required = true, values = values, label = I18n.getString("calendar.month"))) + layout.addAction( + UIButton.createDefaultButton( + id = "download_button", + title = I18n.getString("download"), + responseAction = ResponseAction( + RestResolver.getRestUrl( + this::class.java, + "downloadBirthdayList" + ), targetType = TargetType.POST + ) + ) + ) + LayoutUtils.process(layout) + val data = UmfrageData() + return FormLayoutData(data, layout, createServerData(request)) + } +} \ No newline at end of file From b086e56d0df175ee14b2c65e46a377f0ebb64b1d Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Mon, 3 Apr 2023 13:14:51 +0200 Subject: [PATCH 003/160] create branch --- .../kotlin/org/projectforge/rest/poll/PollCreation.kt | 9 +++++++++ .../kotlin/org/projectforge/rest/poll/PollOverview.kt | 5 +++++ .../rest/{umfrage => poll}/UmfragePageRest.kt | 4 ++-- .../kotlin/org/projectforge/rest/umfrage/UmfrageData.kt | 5 ----- 4 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCreation.kt create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollOverview.kt rename projectforge-rest/src/main/kotlin/org/projectforge/rest/{umfrage => poll}/UmfragePageRest.kt (96%) delete mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/umfrage/UmfrageData.kt diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCreation.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCreation.kt new file mode 100644 index 0000000000..5e47204435 --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCreation.kt @@ -0,0 +1,9 @@ +package org.projectforge.rest.poll + +import org.projectforge.rest.config.Rest +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +class PollCreation { + +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollOverview.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollOverview.kt new file mode 100644 index 0000000000..4345916fe8 --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollOverview.kt @@ -0,0 +1,5 @@ +package org.projectforge.rest.poll + +class PollOverview { + var month: Int = 0 +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/umfrage/UmfragePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/UmfragePageRest.kt similarity index 96% rename from projectforge-rest/src/main/kotlin/org/projectforge/rest/umfrage/UmfragePageRest.kt rename to projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/UmfragePageRest.kt index 317b90fc25..bb5c588961 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/umfrage/UmfragePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/UmfragePageRest.kt @@ -1,4 +1,4 @@ -package org.projectforge.rest.umfrage +package org.projectforge.rest.poll import org.projectforge.business.scripting.I18n import org.projectforge.rest.config.Rest @@ -51,7 +51,7 @@ class UmfragePageRest : AbstractDynamicPageRest() { ) ) LayoutUtils.process(layout) - val data = UmfrageData() + val data = PollOverview() return FormLayoutData(data, layout, createServerData(request)) } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/umfrage/UmfrageData.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/umfrage/UmfrageData.kt deleted file mode 100644 index a1b9b11bae..0000000000 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/umfrage/UmfrageData.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.projectforge.rest.umfrage - -class UmfrageData { - var month: Int = 0 -} \ No newline at end of file From 1cf3aed0e153767e69e16232ac9bc8fc98c32bb4 Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Mon, 3 Apr 2023 13:17:40 +0200 Subject: [PATCH 004/160] rename --- .../main/kotlin/org/projectforge/rest/poll/PollCreation.kt | 4 ---- .../projectforge/rest/poll/{PollOverview.kt => PollData.kt} | 2 +- .../rest/poll/{UmfragePageRest.kt => PollPageRest.kt} | 4 ++-- 3 files changed, 3 insertions(+), 7 deletions(-) rename projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/{PollOverview.kt => PollData.kt} (74%) rename projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/{UmfragePageRest.kt => PollPageRest.kt} (96%) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCreation.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCreation.kt index 5e47204435..1dbce686d4 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCreation.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCreation.kt @@ -1,9 +1,5 @@ package org.projectforge.rest.poll -import org.projectforge.rest.config.Rest -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController - class PollCreation { } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollOverview.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollData.kt similarity index 74% rename from projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollOverview.kt rename to projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollData.kt index 4345916fe8..c16fbda5fc 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollOverview.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollData.kt @@ -1,5 +1,5 @@ package org.projectforge.rest.poll -class PollOverview { +class PollData { var month: Int = 0 } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/UmfragePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt similarity index 96% rename from projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/UmfragePageRest.kt rename to projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index bb5c588961..e7230978e5 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/UmfragePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -13,7 +13,7 @@ import javax.servlet.http.HttpServletRequest @RestController @RequestMapping("${Rest.URL}/umfrage") -class UmfragePageRest : AbstractDynamicPageRest() { +class PollPageRest : AbstractDynamicPageRest() { val months = arrayOf( I18n.getString("calendar.month.january"), @@ -51,7 +51,7 @@ class UmfragePageRest : AbstractDynamicPageRest() { ) ) LayoutUtils.process(layout) - val data = PollOverview() + val data = PollData() return FormLayoutData(data, layout, createServerData(request)) } } \ No newline at end of file From 170d971428e061476cc31f04b1f3d656e44f41b8 Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Tue, 4 Apr 2023 13:12:43 +0200 Subject: [PATCH 005/160] abstractdtopage implemented --- .../kotlin/org/projectforge/rest/poll/Poll.kt | 28 ++++ .../projectforge/rest/poll/PollCreation.kt | 23 +++ .../org/projectforge/rest/poll/PollDO.kt | 63 +++++++++ .../org/projectforge/rest/poll/PollDao.java | 26 ++++ .../projectforge/rest/poll/PollPageRest.kt | 133 ++++++++++++------ 5 files changed, 232 insertions(+), 41 deletions(-) create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollDO.kt create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollDao.java diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt new file mode 100644 index 0000000000..0b28dcba69 --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -0,0 +1,28 @@ +package org.projectforge.rest.poll + +import org.checkerframework.checker.guieffect.qual.UIType +import org.projectforge.business.fibu.EmployeeDO +import org.projectforge.business.fibu.EmployeeSalaryType +import org.projectforge.rest.dto.BaseDTO +import org.projectforge.ui.UIDataType +import java.math.BigDecimal +import java.util.* + +class Poll( + var title: String? = null, + var description: String? = null, + var owner: EmployeeDO? = null, // EmployeeDO -> PFUserDO + var location: String? = null, + var date: Date? = null, + var deadline: Date? = null, + var inputFields: List? = null, + var canSeeResultUsers: List? = null, + var canEditPollUsers: List? = null, + var canVoteInPoll: List? = null +) : BaseDTO() { + class InputField( + var type: UIDataType? = null, + var name: String? = null, + var value: Any? = null, + ) +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCreation.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCreation.kt index 1dbce686d4..ab189031d1 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCreation.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCreation.kt @@ -1,5 +1,28 @@ package org.projectforge.rest.poll +import org.projectforge.business.scripting.I18n +import org.projectforge.business.vacation.model.VacationDO +import org.projectforge.rest.core.PagesResolver +import org.projectforge.rest.core.RestResolver +import org.projectforge.ui.* +import javax.annotation.PostConstruct + class PollCreation { + fun createPollForm() : UILayout{ + + val lc = LayoutContext(PollDO::class.java) + val layout = UILayout(I18n.getString("poll.create")) + /*layout.add(UIRow().add(UIFieldset(UILength(md = 6, lg = 4)) + .add(lc, "name"))) + */ + + layout.add( + UIRow() + .add( + UIFieldset(UILength(md = 6, lg = 4)) + .add(lc, "title", "description", "location", "owner", "deadline") + )) + return layout + } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollDO.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollDO.kt new file mode 100644 index 0000000000..e9f28b7556 --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollDO.kt @@ -0,0 +1,63 @@ +package org.projectforge.rest.poll + +import org.hibernate.search.annotations.Indexed +import org.hibernate.search.annotations.IndexedEmbedded +import org.projectforge.business.fibu.EmployeeDO +import org.projectforge.common.anots.PropertyInfo +import org.projectforge.framework.persistence.api.AUserRightId +import org.projectforge.framework.persistence.entities.DefaultBaseDO +import java.time.LocalDate +import javax.persistence.* + + +@Entity +@Indexed +@Table(name = "t_poll") +@AUserRightId(value = "poll", checkAccess = false) +open class PollDO : DefaultBaseDO() { + + @PropertyInfo(i18nKey = "poll.title") + @get:Column(name = "title", nullable = false) + open var title: String? = null + + @PropertyInfo(i18nKey = "poll.description") + @get:Column(name = "description", nullable = false) + open var description: String? = null + + @PropertyInfo(i18nKey = "poll.owner") + @IndexedEmbedded(depth = 1) + @ManyToOne + @JoinColumn(name = "owner_pk") + open var owner: EmployeeDO? = null + + @PropertyInfo(i18nKey = "poll.location") + @get:Column(name = "location", nullable = false) + open var location: String? = null + + @PropertyInfo(i18nKey = "poll.date") + @get:Column(name = "date", nullable = false) + open var date: LocalDate? = null + + @PropertyInfo(i18nKey = "poll.deadline") + @get:Column(name = "end_date", nullable = false) + open var deadline: LocalDate? = null + + @PropertyInfo(i18nKey = "poll.canSeeResultUsers") + @IndexedEmbedded(depth = 1) + @get:ManyToMany(fetch = FetchType.EAGER) + @get:JoinColumn(name = "canSeeResultUsers", nullable = false) + open var canSeeResultUsers: List? = null + + @PropertyInfo(i18nKey = "poll.canEditPollUsers") + @IndexedEmbedded(depth = 1) + @get:ManyToMany(fetch = FetchType.EAGER) + @get:JoinColumn(name = "canEditPollUsers", nullable = false) + open var canEditPollUsers: List? = null + + @PropertyInfo(i18nKey = "poll.canVoteInPoll") + @IndexedEmbedded(depth = 1) + @get:ManyToMany(fetch = FetchType.EAGER) + @get:JoinColumn(name = "canVoteInPoll", nullable = false) + open var canVoteInPoll: List? = null + +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollDao.java b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollDao.java new file mode 100644 index 0000000000..945bd56c60 --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollDao.java @@ -0,0 +1,26 @@ +package org.projectforge.rest.poll; + +import org.projectforge.business.teamcal.event.model.*; +import org.projectforge.business.user.*; +import org.projectforge.framework.access.*; +import org.projectforge.framework.persistence.api.*; +import org.projectforge.framework.persistence.user.entities.*; +import org.springframework.stereotype.*; + +@Repository +public class PollDao extends BaseDao { + + public PollDao() { + super(PollDO.class); + } + + @Override + public boolean hasAccess(PFUserDO user, PollDO obj, PollDO oldObj, OperationType operationType, boolean throwException) { + return true; + } + + @Override + public PollDO newInstance() { + return null; + } +} diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index e7230978e5..ad01519c0f 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -1,57 +1,108 @@ package org.projectforge.rest.poll +import org.checkerframework.checker.guieffect.qual.UIType +import org.projectforge.business.fibu.EmployeeDO import org.projectforge.business.scripting.I18n +import org.projectforge.business.user.UserRightId +import org.projectforge.business.user.UserRightValue +import org.projectforge.business.vacation.model.VacationDO +import org.projectforge.business.vacation.model.VacationStatus +import org.projectforge.business.vacation.repository.VacationDao +import org.projectforge.business.vacation.service.ConflictingVacationsCache +import org.projectforge.framework.persistence.api.BaseDao +import org.projectforge.framework.persistence.api.MagicFilter +import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext +import org.projectforge.framework.persistence.user.entities.PFUserDO +import org.projectforge.framework.utils.NumberHelper +import org.projectforge.menu.MenuItem +import org.projectforge.menu.MenuItemTargetType +import org.projectforge.rest.VacationExportPageRest import org.projectforge.rest.config.Rest -import org.projectforge.rest.core.AbstractDynamicPageRest -import org.projectforge.rest.core.RestResolver +import org.projectforge.rest.core.* +import org.projectforge.rest.dto.Employee import org.projectforge.rest.dto.FormLayoutData +import org.projectforge.rest.dto.Vacation import org.projectforge.ui.* +import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import java.time.LocalDate import javax.servlet.http.HttpServletRequest @RestController -@RequestMapping("${Rest.URL}/umfrage") -class PollPageRest : AbstractDynamicPageRest() { - - val months = arrayOf( - I18n.getString("calendar.month.january"), - I18n.getString("calendar.month.february"), - I18n.getString("calendar.month.march"), - I18n.getString("calendar.month.april"), - I18n.getString("calendar.month.may"), - I18n.getString("calendar.month.june"), - I18n.getString("calendar.month.july"), - I18n.getString("calendar.month.august"), - I18n.getString("calendar.month.september"), - I18n.getString("calendar.month.october"), - I18n.getString("calendar.month.november"), - I18n.getString("calendar.month.december") - ) - - @GetMapping("dynamic") - fun getForm(request: HttpServletRequest): FormLayoutData { - val layout = UILayout(I18n.getString("menu.birthdayList")) - - val values = ArrayList>() - months.forEachIndexed { index, month -> values.add(UISelectValue(index + 1, month)) } - - layout.add(UISelect("month", required = true, values = values, label = I18n.getString("calendar.month"))) - layout.addAction( - UIButton.createDefaultButton( - id = "download_button", - title = I18n.getString("download"), - responseAction = ResponseAction( - RestResolver.getRestUrl( - this::class.java, - "downloadBirthdayList" - ), targetType = TargetType.POST - ) +@RequestMapping("${Rest.URL}/poll") +class PollPageRest : AbstractDTOPagesRest(PollDao::class.java, "poll.title") { + + @Autowired + private lateinit var pollDao: PollDao + + override fun transformForDB(dto: Poll): PollDO { + val pollDO = PollDO() + dto.copyTo(pollDO) + return pollDO + } + + override fun transformFromDB(obj: PollDO, editMode: Boolean): Poll { + val poll = Poll() + poll.copyFrom(obj) + return poll + } + + override fun createListLayout(request: HttpServletRequest, layout: UILayout, magicFilter: MagicFilter, userAccess: UILayout.UserAccess) { + agGridSupport.prepareUIGrid4ListPage( + request, + layout, + magicFilter, + this, + userAccess = userAccess, + ) + .add(lc, "title", "description", "location") + .add(lc, "owner") + .add(lc, "deadline") + layout.add( + MenuItem( + "export", + i18nKey = "poll.export.title", + url = PagesResolver.getDynamicPageUrl(VacationExportPageRest::class.java), + type = MenuItemTargetType.REDIRECT, ) ) - LayoutUtils.process(layout) - val data = PollData() - return FormLayoutData(data, layout, createServerData(request)) } + + override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { + val lc = LayoutContext(PollDO::class.java) + val layout = UILayout(I18n.getString("poll.create")) + /* // dto.inputFields?.forEachIndexed { field, index -> + if (field.type == UIDataType.BOOLEAN) { + // layout.add() // ID: type[] + // "type[$index]" + // Id: name + } + }*/ + /*layout.add(UIRow().add(UIFieldset(UILength(md = 6, lg = 4)) + .add(lc, "name"))) + */ + + layout.add( + UIRow() + .add( + UIFieldset(UILength(md = 6, lg = 4)) + .add(lc, "title", "description", "location", "owner", "deadline") + )) + + layout.watchFields.addAll( + arrayOf( + "title", + "description", + "location", + "owner", + "deadline" + ) + ) + // layout.addAction() // Button mit Rest-Endpunkt add + return LayoutUtils.processEditPage(layout, dto, this) + } + + // PostMapping add } \ No newline at end of file From 9e78e1261326b19d0d0edb04db3e9c2b715ff0d9 Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Tue, 4 Apr 2023 13:14:32 +0200 Subject: [PATCH 006/160] deleted files --- .../projectforge/rest/poll/PollCreation.kt | 28 ------------------- .../org/projectforge/rest/poll/PollData.kt | 5 ---- 2 files changed, 33 deletions(-) delete mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCreation.kt delete mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollData.kt diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCreation.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCreation.kt deleted file mode 100644 index ab189031d1..0000000000 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCreation.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.projectforge.rest.poll - -import org.projectforge.business.scripting.I18n -import org.projectforge.business.vacation.model.VacationDO -import org.projectforge.rest.core.PagesResolver -import org.projectforge.rest.core.RestResolver -import org.projectforge.ui.* -import javax.annotation.PostConstruct - -class PollCreation { - - fun createPollForm() : UILayout{ - - val lc = LayoutContext(PollDO::class.java) - val layout = UILayout(I18n.getString("poll.create")) - /*layout.add(UIRow().add(UIFieldset(UILength(md = 6, lg = 4)) - .add(lc, "name"))) - */ - - layout.add( - UIRow() - .add( - UIFieldset(UILength(md = 6, lg = 4)) - .add(lc, "title", "description", "location", "owner", "deadline") - )) - return layout - } -} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollData.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollData.kt deleted file mode 100644 index c16fbda5fc..0000000000 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollData.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.projectforge.rest.poll - -class PollData { - var month: Int = 0 -} \ No newline at end of file From 7d246b4118a494c5b25f778af5195ac59f8e3c58 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Tue, 4 Apr 2023 13:16:10 +0200 Subject: [PATCH 007/160] init --- .../officeTemplates/PollResultTemplate.xlsx | Bin 0 -> 8374 bytes .../rest/poll/Detail/View/PollDetailRest.kt | 17 ++++ .../rest/poll/Exel/ExcelExport.kt | 96 ++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 projectforge-business/src/main/resources/officeTemplates/PollResultTemplate.xlsx create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Detail/View/PollDetailRest.kt create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt diff --git a/projectforge-business/src/main/resources/officeTemplates/PollResultTemplate.xlsx b/projectforge-business/src/main/resources/officeTemplates/PollResultTemplate.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c88023f767d3527be3e8a4e7dc82eb3525eec971 GIT binary patch literal 8374 zcmeHM1zVKc+8!FDrArvPyFps%lnzn4hfb+MhOPmI4h5uBKpG^3k(8DO=?)1I@Ei9z z`|Q2l`}+mwthugtT{CO$XWm)w{lvN-Emag$VgNb-6951J0fkTcMm3NC08CT>fCzwz zY$6YG@w9gFG}rZWwe~RM_H}lmdyR_Bnh!umeE)yrzjy~Kk_R<?aI~TqX2(uP+Qln z$hm&Rc8RKyA%-qNa~$u)%cr!hd56A#CRHM+&B40K|Hj824w)rCIrwrq_?=0bi+^QY zB(6&Ko4<7_GPOKvX$^|f^l_UzM}x$b)ceu*tf%}PbxC(YMpv;33b*J}xD?-Nots%&nZd&J?mbiEdwTX=D%R&+J7P73wu3GBs)WOQ3ZK(M%)VXK z+!nr967MymH7p|SrxZ^*h72?od~)4HX%@79K^<2}Zi4gSftqR){ERI2heW>Qp6+*J z`GuL${4zGi(*u`f!S*5_Py&NkbBGMgUWrUAHHs?=(wWVq*fn@H{O|;t8?Jv%?e;Xr z5wGtdO-G=lt0H50e_fxbCMkvF_w`e0`ilw%2JK{VN8RWjS9IZLI+)SYpHMAfy{hDs)>(EHy>Nh6-ZS(^PR3J4eN9)*vSn@=oRV7or%&KtEW1g4)b+ zmkR2U6$y0>n2guy2R|jcJ+5&S(CM%O@7YA6~N>q;?DH7+L zYJ}oly4{v-qVL-e%dURNzFQQKeN`)CPQVdc7z@`~BG991x4Ul@m`c!gp5vpy9h#u4 zKO$n*^9o*Ju*=wAc`W@^S%sz^P5UVQAUAjfKtOy_@}7zb$G2msIv>w`fI%0{!TccW z;{N!9pa5<#Z(O$^udT~^X6T-ujlP zC(}t0yYWk|9?zEa{CDXmqtdtq2NChQ%l7YLUDjimzmCr?)mE)#p*Fw5{UjNu+CMco z{H)C~wTx)}WxUVqcZJV^xUnZzv#1A-Hdy}N*wRW9L&81X?`<~yX4M!#7S~U0$TZTe z2|u7P?2#0$UZ6VkIC+Mxky$BcK`fd|&KJwhut({qiz5f`;h$5Pb1FXpe>fKuh0J=> zTcxyd1OR0QpVVyo%d&6ln<)zo<%^e zg_+>3a%6CSpD3pEgHEALU-g&9m^xBjWDXhz6iIZ|X0ctDLZ6551?JJBpN3*j^dvdk z7_DRkJ#D%}-I~ZSZ%dq_Fls+DzE`>GtfGt0Ro?1l*zeDI(Hb#4XCDK zJO5+j3hgy$J8GowAS!-hbS7qxD6rZl2ISLMIb$kR1FI9S5=MZYlNjMt0v$Z#ZS9Xh7Ktb#@y1Ub3dLtTtw)CvY@f|o(B=dz zoAt%wUF2fZ%RGmtnETE1K0b?1^3t8swd-?#@r-nt zNqjXVhdbOgY0r%{0Lsh+Uy2n*{=`JBX=N+A}X(`fsPrUA;z>_@3CsVID*C7|~=MmtAkU-Q~LBH8)Bqa2h1 zG-H*M^o;GosFg5sV%ik7;gHAx_NzJ9Bd3z}HQuK09_@4eqVQLjQqjH-`ungF*ZyEe8w}H{gF(`$ahE|Tw4(a=~v@8ZmSkt0( z4p%mN2E{(dtuLJ=_5LZVB`naM&ndXFWmEG z9;%(Sj4Jcze3PVmfv=6lUI`b^0(nb~c@H0v7Bj$}0FmQz<2u&>G9-8#d>S>U>_1JE1ci zvws%ln}X9T7_G9{JUeM0CM2n;%SJSGnK`y!shc5>w4a(LY$=_Tqe!QcoB?|E6l|dt z5XcW?*az`UU9fFy6l$R`e7sE6X^W?!g^|c{Dx|p|VSa+)3&egtAy`iCP)tfZmLKHV zk?w2?tc`5s2eOL{P`absropX|zw|`sCTrsBC!@=fPtI}NQp#+ISD7EpTsNdj;mQ;c zRtPrqO&e8vhT%Adj923=w)0TXlbA5Ewd3<+$9$}Essaz}-MaF!$F>2m%bDJHrxl7HbBG(2itQt0sm@;5kg20LyeYsC+}tcnDlrKS(~gX%$aOyvx}d5 zW#gOK;Ph?eD3jaJ3!!MPl?RsfJxvox22-Yxh=?<*-Q4U^^#%2p@unU=+7@U{(s@Yj z_tQSQyyOx9X{9I$_F=(tuj6Pe_6-+lV-ZLzBCHi+!Rn0jwJV2H239A zNx4yglQ7VQO90Jdv+~1L1}dz1WbKF(c{MJPr;k0NEYdy#qf2*=%@`TdlD=b0@w){A zf#XlqGG=Hkfu{U3Vihx(*Lu&$eP%DW=^K+wq4H@XX1(cA`?WnTFV`|AY@iCv7XogW zY|(90YAq`PLrcJ2inS1E+-iq<4auPh(R(gBgNZ^(*-0Lc!^jQmq5a#Ye4>|ToD-Y1 zeO1mH*`GgUXjV=x^`*bI6#OvTdY;h(#>+C28jb6?k5s<@%)r7-*1M`~3l)2Rr^P0R z-Y2R?$D3C3GkU-UDNCenD3ts}J+oSNvT`!~$%t~%%+B^0>Qe@-E7=aw9Cx!2Jcj1) zS@lbiiC*5NwyPsEXz$i8g#&OdHWnuiRTJMA!;mb39hY$k8<+=(Y$Ef!Tt9I|8m^8$ znrtrX+tg@|YPUb*m3eM8CBdh>WO$R!8qghFyzfTFYpZqKMAE5Q`Zh|(TZ33M%k{_m zx;GJg#`2jPQ_Gvm!Rm{_&7Daf3*5C$J4D{(sIPB{G5OJyQO zB)8LHs@2BfIiuJ`HS7xrYoRF$fR>Z4aDuCi!rG)ZnuW(J*kX z(H#wQwLqu}tNyf>0z>v)SS$;XB-Ca$j&Nh=DpeFgH8g^*~u5z8->7z;d zt(Hyb9-VehaW9p|;yY0SoQYF7=R`{1o?wNj1XY8&L=U*@Siw1LS>N{M1ksWIY1jh1 zLJy<2u4*!>nrCp>#M#@GY@h(+GQxvB9FJ+i$OH9}xj#zAIJ#3(0Dn2*YL>`W;l?sg z_(8r2R%^Hp!#)|5ug9U87N**+v4KR*$saxV++SNdgafB#pla{@YA!Xf zt*xk;*TOAnhvVC8VN>(ZX&mt!0iY6>&A>Cme$12eQCzFUoM6chhWN z9TxpTV|$Re&6Ut28hh8VD(^7kPVJJ^)#)cVt=}f#)DmX%gTYE9^@P zmf_|JGE~2(Sbf(joHSA&$(pKvGAqx<#~(0jxb>)1`w~clt&I18Hs0~8(VGNwU49jR zGE*81C7a@0o{iDjSS&wgv)#^n$?+KxK2x~I*nOQdI{qFuCp@#;Sv7EqX_~ZA@3N0O z<{jLRJKvORLrDd4D`HOA*$%()XDpq8HEnmG%bY(x^|k4Uu4<5+Xl>Oiv6&17oVBsf zM*LV(Cf*RROC+q3QBmF+9&dKO{DK03n6bAGm|ZTt_*zJeq_5Fg$}WuAC7~tDd2^7dUybtYno9&rN%2${(W zWU1v2a`oVO?&|vQe#rmCCB)8UL*Kj1{}d!qF7P0SaoH}c5E*^#LMng5T7a9st(7<_ zvnr&Q5}s=d8Ykm$E+~C_P)`NVyZ#mhZF$(py~oE)^#TZsF}2!OFR73wwlD~4y{4~I zSmAJeZwb^$@2$MMl6AJ&=7sj9)P`$@+eNmdroaVf<&10gjY{R6LPg^+D0b&4+weD= zA8DS7kAfHG=o~Cx0m`{vlKernnk^;&f%(Dz zUMale0j@aHmUFb&JG|zJUuGLJ%ZAw!)re*A1r00gq=uT?J=k>dvv?7h_ zI(>{R!9mV!gr5{~SM`{%zSpMB?&l5&g1-&#xm$GedS3B^WG46W1#jag$ zN9*@Ky5heCnY443IinDA1Vx@rkD{G-VaK`f8i}mn8@8X{aN%^W+Fzgg`@J2I-neOd zn+ZM2GwDP`zQ6NXH+XqY3Bl(8#46#R`D_VtxBefY5lsH~NP(KG_V5z-Eu(eH!vnJF zNs`9B^Xph5z*Ny7VDln)m=ng+5fhAk z%TMw3Tg=jV#9EOQ^-iraD9?$ofKic{*W>49t)hy893PkdV0m z|6h;!_uc+|{)djUmg-*({IxswJMiZmfr!z6>XrQp{Akn`+&Trs96t7>Qzp4p;K(!F{58`)!)fRp=@M}Hvhk;Sj|J?ZB zs-s`6{52!~VFMv?PyhhG=Eq;re~mwXMt`CD6Z-dPq@{|6cs2k47UCs^V3fs!pYQ$; Dfc}u0 literal 0 HcmV?d00001 diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Detail/View/PollDetailRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Detail/View/PollDetailRest.kt new file mode 100644 index 0000000000..ffbd65990c --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Detail/View/PollDetailRest.kt @@ -0,0 +1,17 @@ +package org.projectforge.rest.poll.Detail.View + +import org.projectforge.rest.config.Rest +import org.projectforge.rest.config.RestUtils +import org.projectforge.rest.core.AbstractDynamicPageRest +import org.projectforge.rest.poll.Exel.ExcelExport +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import javax.servlet.http.HttpServletRequest + +class PollDetailRest : AbstractDynamicPageRest(){ + + +} diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt new file mode 100644 index 0000000000..2db854dbf2 --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt @@ -0,0 +1,96 @@ +package org.projectforge.rest.poll.Exel + +import de.micromata.merlin.excel.ExcelRow +import de.micromata.merlin.excel.ExcelSheet +import de.micromata.merlin.excel.ExcelWorkbook +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.core.io.ClassPathResource +import java.io.IOException +import java.time.LocalDate +import java.util.* + + +class ExcelExport { + + private val log: Logger = LoggerFactory.getLogger(ExcelExport::class.java) + + private val FIRST_DATA_ROW_NUM = 5 + + + fun getExcel(): ByteArray? { + var excelSheet: ExcelSheet? = null + var emptyRow: ExcelRow? = null + + val classPathResource = ClassPathResource("officeTemplates/PollResultTemplate" + ".xlsx") + + + try { + ExcelWorkbook(classPathResource.inputStream, classPathResource.file.name).use { workbook -> + excelSheet = workbook.getSheet(0) + emptyRow = excelSheet!!.getRow(5) + val anzNewRows = 3 + excelSheet!!.getRow(0).getCell(0).setCellValue(contentOfCell) + createNewRow(excelSheet, emptyRow, anzNewRows) + var hourCounter = 0.0 + for (i in 0 until anzNewRows) { + hourCounter = setNewRows(hourCounter, excelSheet!!, i) + } + return returnByteFile(excelSheet!!) + } + } catch (e: NullPointerException) { + e.printStackTrace() + } catch (e: IOException) { + e.printStackTrace() + } + return null + } + + private fun setNewRows(hourCounter: Double, excelSheet: ExcelSheet, cell: Int): Double { + var hourCounter = hourCounter + var description: String? = "" + + val excelRow = excelSheet.getRow(FIRST_DATA_ROW_NUM + cell) + + excelRow.getCell(0).setCellValue("test1") + excelRow.getCell(1).setCellValue("test2") + excelRow.getCell(3).setCellValue("test3") + excelRow.getCell(4).setCellValue("test4") + excelRow.getCell(5).setCellValue("test5") + + + val puffer = description + var counterOfBreaking = -1 + var counterOfOverlength = 0 + val pufferSplit = puffer!!.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + + // check for line-breaks + for (i in pufferSplit.indices) { + counterOfBreaking++ + counterOfOverlength += pufferSplit[i].length / 70 + } + excelRow.setHeight((14 + counterOfOverlength * 14 + counterOfBreaking * 14).toFloat()) + return hourCounter + } + + + private fun createNewRow(excelSheet: ExcelSheet?, emptyRow: ExcelRow?, anzNewRows: Int) { + if (excelSheet == null || emptyRow == null) { + log.error("in createNewRow(...) excelSheet or emptyRow is null") + return + } + for (i in 1 until anzNewRows) { + Objects.requireNonNull( + excelSheet.getRow(FIRST_DATA_ROW_NUM) + ).copyAndInsert( + emptyRow.sheet + ) + } + } + private fun returnByteFile(excelSheet: ExcelSheet): ByteArray? { + excelSheet.excelWorkbook.use { workbook -> + val byteArrayOutputStream = workbook.asByteArrayOutputStream + return byteArrayOutputStream.toByteArray() + } + } +} \ No newline at end of file From 31ea734a8b7edba0ee1b8e6d52595749ce5fea9f Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Tue, 4 Apr 2023 15:35:02 +0200 Subject: [PATCH 008/160] ride data in Excel --- .../rest/poll/Exel/ExcelExport.kt | 22 ++++++++-------- .../projectforge/rest/poll/PollPageRest.kt | 26 ++++++++++++++++++- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt index 2db854dbf2..49bdc5ebcb 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt @@ -19,24 +19,24 @@ class ExcelExport { fun getExcel(): ByteArray? { - var excelSheet: ExcelSheet? = null - var emptyRow: ExcelRow? = null + //var excelSheet: ExcelSheet? = null + //var emptyRow: ExcelRow? = null val classPathResource = ClassPathResource("officeTemplates/PollResultTemplate" + ".xlsx") try { ExcelWorkbook(classPathResource.inputStream, classPathResource.file.name).use { workbook -> - excelSheet = workbook.getSheet(0) - emptyRow = excelSheet!!.getRow(5) + val excelSheet = workbook.getSheet(0) + val emptyRow = excelSheet.getRow(5) val anzNewRows = 3 - excelSheet!!.getRow(0).getCell(0).setCellValue(contentOfCell) + //excelSheet.getRow(0).getCell(0).setCellValue(contentOfCell) createNewRow(excelSheet, emptyRow, anzNewRows) var hourCounter = 0.0 for (i in 0 until anzNewRows) { - hourCounter = setNewRows(hourCounter, excelSheet!!, i) + hourCounter = setNewRows(hourCounter, excelSheet, i) } - return returnByteFile(excelSheet!!) + return returnByteFile(excelSheet) } } catch (e: NullPointerException) { e.printStackTrace() @@ -47,8 +47,8 @@ class ExcelExport { } private fun setNewRows(hourCounter: Double, excelSheet: ExcelSheet, cell: Int): Double { - var hourCounter = hourCounter - var description: String? = "" + val hourCounter = hourCounter + val description: String = "" val excelRow = excelSheet.getRow(FIRST_DATA_ROW_NUM + cell) @@ -60,9 +60,9 @@ class ExcelExport { val puffer = description - var counterOfBreaking = -1 + var counterOfBreaking = 0 var counterOfOverlength = 0 - val pufferSplit = puffer!!.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val pufferSplit = puffer.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() // check for line-breaks for (i in pufferSplit.indices) { diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index e7230978e5..0e6d219f56 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -2,11 +2,19 @@ package org.projectforge.rest.poll import org.projectforge.business.scripting.I18n import org.projectforge.rest.config.Rest +import org.projectforge.rest.config.RestUtils import org.projectforge.rest.core.AbstractDynamicPageRest import org.projectforge.rest.core.RestResolver import org.projectforge.rest.dto.FormLayoutData +import org.projectforge.rest.poll.Detail.View.PollDetailRest +import org.projectforge.rest.poll.Exel.ExcelExport import org.projectforge.ui.* +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.core.io.Resource +import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import javax.servlet.http.HttpServletRequest @@ -45,7 +53,7 @@ class PollPageRest : AbstractDynamicPageRest() { responseAction = ResponseAction( RestResolver.getRestUrl( this::class.java, - "downloadBirthdayList" + "Export" ), targetType = TargetType.POST ) ) @@ -54,4 +62,20 @@ class PollPageRest : AbstractDynamicPageRest() { val data = PollData() return FormLayoutData(data, layout, createServerData(request)) } + + private val log: Logger = LoggerFactory.getLogger(PollDetailRest::class.java) + + @PostMapping("Export") + fun export(request: HttpServletRequest) : ResponseEntity? { + val ihkExporter = ExcelExport() + val bytes: ByteArray? = ihkExporter + .getExcel() + val filename = ("test.xlsx") + + if (bytes == null || bytes.size == 0) { + log.error("Oups, xlsx has zero size. Filename: $filename") + return null; + } + return RestUtils.downloadFile(filename, bytes) + } } \ No newline at end of file From c56c87da9f84402e57a81822b0248ec5dadc39c4 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 5 Apr 2023 09:54:57 +0200 Subject: [PATCH 009/160] add some types --- .../projectforge/rest/poll/PollPageRest.kt | 4 +++- .../types/AntwortM\303\266glichkeiten.kt" | 9 +++++++++ .../rest/poll/types/FrageTypen.kt | 19 +++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 "projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/AntwortM\303\266glichkeiten.kt" create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/FrageTypen.kt diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index ad01519c0f..e2b9ec2150 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -72,7 +72,9 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { val lc = LayoutContext(PollDO::class.java) - val layout = UILayout(I18n.getString("poll.create")) + + val layout = super.createEditLayout(dto, userAccess).add(UILabel("poll.create")) + /* // dto.inputFields?.forEachIndexed { field, index -> if (field.type == UIDataType.BOOLEAN) { // layout.add() // ID: type[] diff --git "a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/AntwortM\303\266glichkeiten.kt" "b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/AntwortM\303\266glichkeiten.kt" new file mode 100644 index 0000000000..4fd8e0dc5e --- /dev/null +++ "b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/AntwortM\303\266glichkeiten.kt" @@ -0,0 +1,9 @@ +package org.projectforge.rest.poll.types + +class AntwortMöglichkeiten ( + id: String, + antwort: String + +){ + +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/FrageTypen.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/FrageTypen.kt new file mode 100644 index 0000000000..9ca1d3d083 --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/FrageTypen.kt @@ -0,0 +1,19 @@ +package org.projectforge.rest.poll.types + + +class Frage( + val uid: String, + val question: String, + val type: BaseType, + var antworten: List, + var perrent: String? +){ + +} +enum class BaseType { + JaNeinFrage, + DatumsAbfrage, + MultipleChoices, + FreiTextFrage, + DropDownFrage +} \ No newline at end of file From a8a0aedcbe82506a082618f6a3e09adaf4a7598b Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Wed, 5 Apr 2023 16:30:38 +0200 Subject: [PATCH 010/160] poll entries in database possible --- .../org/projectforge/business/poll/PollDO.kt | 64 +++++++++++ .../projectforge/business}/poll/PollDao.java | 8 +- .../V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql | 15 +++ .../kotlin/org/projectforge/rest/poll/Poll.kt | 16 +-- .../org/projectforge/rest/poll/PollDO.kt | 63 ----------- .../projectforge/rest/poll/PollPageRest.kt | 101 +++++++++++------- 6 files changed, 156 insertions(+), 111 deletions(-) create mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt rename {projectforge-rest/src/main/kotlin/org/projectforge/rest => projectforge-business/src/main/kotlin/org/projectforge/business}/poll/PollDao.java (78%) create mode 100644 projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql delete mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollDO.kt diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt new file mode 100644 index 0000000000..0968ffdf01 --- /dev/null +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -0,0 +1,64 @@ +package org.projectforge.business.poll + +import org.hibernate.search.annotations.Indexed +import org.hibernate.search.annotations.IndexedEmbedded +import org.projectforge.common.anots.PropertyInfo +import org.projectforge.framework.persistence.api.AUserRightId +import org.projectforge.framework.persistence.entities.DefaultBaseDO +import org.projectforge.framework.persistence.user.entities.PFUserDO +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.DependsOn +import java.time.LocalDate +import javax.persistence.* + + +@Entity +@Indexed +@Table(name = "t_poll") +@AUserRightId(value = "poll", checkAccess = false) +@DependsOn("org.projectforge.framework.persistence.user.entities.PFUserDO") +open class PollDO : DefaultBaseDO() { + + @PropertyInfo(i18nKey = "poll.title") + @get:Column(name = "title", nullable = true, length = 1000) + open var title: String? = null + + @PropertyInfo(i18nKey = "poll.description") + @get:Column(name = "description", nullable = true, length = 10000) + open var description: String? = null + + /* @PropertyInfo(i18nKey = "poll.owner") + @IndexedEmbedded(depth = 1) + @ManyToOne + @JoinColumn(name = "owner_pk") + open var owner: PFUserDO? = null*/ + + @PropertyInfo(i18nKey = "poll.location") + @get:Column(name = "location", nullable = true) + open var location: String? = null + +/* @PropertyInfo(i18nKey = "poll.date") + @get:Column(name = "date", nullable = true) + open var date: LocalDate? = null + + @PropertyInfo(i18nKey = "poll.deadline") + @get:Column(name = "deadline", nullable = true) + open var deadline: LocalDate? = null + + @PropertyInfo(i18nKey = "poll.inputlist") + @get:Column(name = "input_list", nullable = true, length = 10000) + open var inputFields: String? = null*/ + + /* @PropertyInfo(i18nKey = "poll.canSeeResultUsers") + @get:Column(name = "canSeeResultUsers", nullable = true) + open var canSeeResultUsers: String? = null + + @PropertyInfo(i18nKey = "poll.canEditPollUsers") + @get:Column(name = "canEditPollUsers", nullable = true) + open var canEditPollUsers: String? = null + + @PropertyInfo(i18nKey = "poll.canVoteInPoll") + @get:Column(name = "canVoteInPoll", nullable = true) + open var canVoteInPoll: String? = null*/ + +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollDao.java b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java similarity index 78% rename from projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollDao.java rename to projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java index 945bd56c60..7320b06e5d 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollDao.java +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java @@ -1,10 +1,9 @@ -package org.projectforge.rest.poll; +package org.projectforge.business.poll; -import org.projectforge.business.teamcal.event.model.*; -import org.projectforge.business.user.*; import org.projectforge.framework.access.*; import org.projectforge.framework.persistence.api.*; import org.projectforge.framework.persistence.user.entities.*; +import org.springframework.context.annotation.*; import org.springframework.stereotype.*; @Repository @@ -21,6 +20,7 @@ public boolean hasAccess(PFUserDO user, PollDO obj, PollDO oldObj, OperationType @Override public PollDO newInstance() { - return null; + return new PollDO(); } + } diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql new file mode 100644 index 0000000000..b73c349350 --- /dev/null +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql @@ -0,0 +1,15 @@ +-- Table with synchronize infos (Sipgate) + +CREATE TABLE T_POLL +( + pk INTEGER NOT NULL, + deleted BOOLEAN NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE, + last_update TIMESTAMP WITHOUT TIME ZONE, + title CHARACTER VARYING(1000), + description CHARACTER VARYING(1000), + location CHARACTER VARYING(1000), +); + +ALTER TABLE T_POLL + ADD CONSTRAINT t_poll_pkey PRIMARY KEY (pk); diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index 0b28dcba69..b1d48b9547 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -1,24 +1,24 @@ package org.projectforge.rest.poll -import org.checkerframework.checker.guieffect.qual.UIType -import org.projectforge.business.fibu.EmployeeDO -import org.projectforge.business.fibu.EmployeeSalaryType +import org.projectforge.business.poll.PollDO +import org.projectforge.framework.persistence.user.entities.PFUserDO import org.projectforge.rest.dto.BaseDTO import org.projectforge.ui.UIDataType -import java.math.BigDecimal import java.util.* class Poll( var title: String? = null, var description: String? = null, - var owner: EmployeeDO? = null, // EmployeeDO -> PFUserDO +/* + var owner: PFUserDO? = null, +*/ var location: String? = null, var date: Date? = null, var deadline: Date? = null, var inputFields: List? = null, - var canSeeResultUsers: List? = null, - var canEditPollUsers: List? = null, - var canVoteInPoll: List? = null + var canSeeResultUsers: String? = null, + var canEditPollUsers: String? = null, + var canVoteInPoll: String? = null ) : BaseDTO() { class InputField( var type: UIDataType? = null, diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollDO.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollDO.kt deleted file mode 100644 index e9f28b7556..0000000000 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollDO.kt +++ /dev/null @@ -1,63 +0,0 @@ -package org.projectforge.rest.poll - -import org.hibernate.search.annotations.Indexed -import org.hibernate.search.annotations.IndexedEmbedded -import org.projectforge.business.fibu.EmployeeDO -import org.projectforge.common.anots.PropertyInfo -import org.projectforge.framework.persistence.api.AUserRightId -import org.projectforge.framework.persistence.entities.DefaultBaseDO -import java.time.LocalDate -import javax.persistence.* - - -@Entity -@Indexed -@Table(name = "t_poll") -@AUserRightId(value = "poll", checkAccess = false) -open class PollDO : DefaultBaseDO() { - - @PropertyInfo(i18nKey = "poll.title") - @get:Column(name = "title", nullable = false) - open var title: String? = null - - @PropertyInfo(i18nKey = "poll.description") - @get:Column(name = "description", nullable = false) - open var description: String? = null - - @PropertyInfo(i18nKey = "poll.owner") - @IndexedEmbedded(depth = 1) - @ManyToOne - @JoinColumn(name = "owner_pk") - open var owner: EmployeeDO? = null - - @PropertyInfo(i18nKey = "poll.location") - @get:Column(name = "location", nullable = false) - open var location: String? = null - - @PropertyInfo(i18nKey = "poll.date") - @get:Column(name = "date", nullable = false) - open var date: LocalDate? = null - - @PropertyInfo(i18nKey = "poll.deadline") - @get:Column(name = "end_date", nullable = false) - open var deadline: LocalDate? = null - - @PropertyInfo(i18nKey = "poll.canSeeResultUsers") - @IndexedEmbedded(depth = 1) - @get:ManyToMany(fetch = FetchType.EAGER) - @get:JoinColumn(name = "canSeeResultUsers", nullable = false) - open var canSeeResultUsers: List? = null - - @PropertyInfo(i18nKey = "poll.canEditPollUsers") - @IndexedEmbedded(depth = 1) - @get:ManyToMany(fetch = FetchType.EAGER) - @get:JoinColumn(name = "canEditPollUsers", nullable = false) - open var canEditPollUsers: List? = null - - @PropertyInfo(i18nKey = "poll.canVoteInPoll") - @IndexedEmbedded(depth = 1) - @get:ManyToMany(fetch = FetchType.EAGER) - @get:JoinColumn(name = "canVoteInPoll", nullable = false) - open var canVoteInPoll: List? = null - -} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index ad01519c0f..2d280d00da 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -1,42 +1,24 @@ package org.projectforge.rest.poll -import org.checkerframework.checker.guieffect.qual.UIType -import org.projectforge.business.fibu.EmployeeDO -import org.projectforge.business.scripting.I18n -import org.projectforge.business.user.UserRightId -import org.projectforge.business.user.UserRightValue -import org.projectforge.business.vacation.model.VacationDO -import org.projectforge.business.vacation.model.VacationStatus -import org.projectforge.business.vacation.repository.VacationDao -import org.projectforge.business.vacation.service.ConflictingVacationsCache -import org.projectforge.framework.persistence.api.BaseDao +import org.projectforge.business.poll.PollDO +import org.projectforge.business.poll.PollDao import org.projectforge.framework.persistence.api.MagicFilter -import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext -import org.projectforge.framework.persistence.user.entities.PFUserDO -import org.projectforge.framework.utils.NumberHelper import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType import org.projectforge.rest.VacationExportPageRest import org.projectforge.rest.config.Rest import org.projectforge.rest.core.* -import org.projectforge.rest.dto.Employee -import org.projectforge.rest.dto.FormLayoutData -import org.projectforge.rest.dto.Vacation import org.projectforge.ui.* -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.web.bind.annotation.GetMapping +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import java.time.LocalDate import javax.servlet.http.HttpServletRequest @RestController @RequestMapping("${Rest.URL}/poll") class PollPageRest : AbstractDTOPagesRest(PollDao::class.java, "poll.title") { - @Autowired - private lateinit var pollDao: PollDao - override fun transformForDB(dto: Poll): PollDO { val pollDO = PollDO() dto.copyTo(pollDO) @@ -72,26 +54,18 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { val lc = LayoutContext(PollDO::class.java) - val layout = UILayout(I18n.getString("poll.create")) - /* // dto.inputFields?.forEachIndexed { field, index -> - if (field.type == UIDataType.BOOLEAN) { - // layout.add() // ID: type[] - // "type[$index]" - // Id: name - } - }*/ - /*layout.add(UIRow().add(UIFieldset(UILength(md = 6, lg = 4)) - .add(lc, "name"))) - */ - + val obj = PollDO() + dto.copyTo(obj) + val layout = super.createEditLayout(dto, userAccess) layout.add( UIRow() .add( UIFieldset(UILength(md = 6, lg = 4)) .add(lc, "title", "description", "location", "owner", "deadline") )) + .add(UIButton.createAddButton(responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST))) - layout.watchFields.addAll( + layout.watchFields.addAll( arrayOf( "title", "description", @@ -100,9 +74,64 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. "deadline" ) ) - // layout.addAction() // Button mit Rest-Endpunkt add + updateStats(dto) return LayoutUtils.processEditPage(layout, dto, this) } + override fun onWatchFieldsUpdate( + request: HttpServletRequest, + dto: Poll, + watchFieldsTriggered: Array? + ): ResponseEntity { + val title = dto.title + val description = dto.description + val location = dto.location +/* + val owner = dto.owner +*/ + val deadline = dto.deadline + + updateStats(dto) + val userAccess = UILayout.UserAccess() + val poll = PollDO() + dto.copyTo(poll) + checkUserAccess(poll, userAccess) + return ResponseEntity.ok( + ResponseAction(targetType = TargetType.UPDATE) + .addVariable("data", dto) + .addVariable("ui", createEditLayout(dto, userAccess)) + ) + } + + private fun updateStats(dto: Poll) { + + val title = dto.title + val description = dto.description + val location = dto.location +/* + val owner = dto.owner +*/ + val deadline = dto.deadline + + val pollDO = PollDO() + dto.copyTo(pollDO) + } + // PostMapping add + @PostMapping("/add") + fun abc(){ + + } + + + /*dto.inputFields?.forEachIndexed { field, index -> + if (field.type == msc) { + layout.add() // + "type[$index]" + Id: name + } + } + layout.add(UIRow().add(UIFieldset(UILength(md = 6, lg = 4)) + .add(lc, "name"))) + */ } \ No newline at end of file From 8987336417cc141b6162ddd77d61a7c20b42a788 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 5 Apr 2023 16:34:17 +0200 Subject: [PATCH 011/160] send mail works --- .../projectforge/rest/poll/PollMailService.kt | 29 +++++++++++++++ .../projectforge/rest/poll/PollPageRest.kt | 35 ++++++++++++++++--- .../poll/types/{FrageTypen.kt => Frage.kt} | 15 ++++---- .../projectforge/rest/poll/types/FrageDO.kt | 4 +++ site/features.adoc | 2 +- 5 files changed, 71 insertions(+), 14 deletions(-) create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt rename projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/{FrageTypen.kt => Frage.kt} (99%) create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/FrageDO.kt diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt new file mode 100644 index 0000000000..e958bcf955 --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt @@ -0,0 +1,29 @@ +package org.projectforge.rest.poll + +import org.projectforge.mail.Mail +import org.projectforge.mail.MailAttachment +import org.projectforge.mail.SendMail +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Service + +@Service +class PollMailService { + + @Autowired + private lateinit var sendMail: SendMail + + + fun sendMail(subject: String, content: String, mailAttachments: List?) { + + val address ="j.bernst@micrmomata.de" + val mail = Mail() + mail.subject = subject + mail.contentType = Mail.CONTENTTYPE_HTML + mail.setTo(address) + mail.content = content + sendMail.send(mail, attachments = mailAttachments) + + } + + +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index a247aeb4be..3c95027d27 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -11,9 +11,7 @@ import org.projectforge.business.vacation.repository.VacationDao import org.projectforge.business.vacation.service.ConflictingVacationsCache import org.projectforge.framework.persistence.api.BaseDao import org.projectforge.framework.persistence.api.MagicFilter -import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext -import org.projectforge.framework.persistence.user.entities.PFUserDO -import org.projectforge.framework.utils.NumberHelper +import org.projectforge.mail.MailAttachment import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType import org.projectforge.rest.VacationExportPageRest @@ -28,11 +26,12 @@ import org.slf4j.LoggerFactory import org.springframework.core.io.Resource import org.springframework.http.ResponseEntity import org.springframework.beans.factory.annotation.Autowired +import org.springframework.scheduling.annotation.Scheduled import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import java.time.LocalDate +import java.time.LocalDateTime import javax.servlet.http.HttpServletRequest @RestController @@ -45,6 +44,9 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @Autowired private lateinit var pollDao: PollDao + @Autowired + private lateinit var pollMailService: PollMailService + override fun transformForDB(dto: Poll): PollDO { val pollDO = PollDO() dto.copyTo(pollDO) @@ -124,6 +126,29 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return LayoutUtils.processEditPage(layout, dto, this) } + @Scheduled(fixedRate = 5000) + fun cronJobSch() { + val ihkExporter = ExcelExport() + val exel = ihkExporter + .getExcel() + + + + val attachment = object : MailAttachment { + override fun getFilename(): String { + return "test"+ "_" + LocalDateTime.now().year + ".xlsx" + } + + override fun getContent(): ByteArray? { + return exel + } + } + + val list = mutableListOf() + list.add(attachment) + pollMailService.sendMail("test","user",list) + } + @PostMapping("Export") fun export(request: HttpServletRequest) : ResponseEntity? { @@ -133,7 +158,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val filename = ("test.xlsx") if (bytes == null || bytes.size == 0) { - log.error("Oups, xlsx has zero size. Filename: $filename") + log.error("Oups, xlsx has zero s Date: Thu, 6 Apr 2023 13:50:14 +0200 Subject: [PATCH 012/160] Database model working --- .../org/projectforge/business/poll/PollDO.kt | 19 +++++++++---------- .../V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql | 4 ++++ .../kotlin/org/projectforge/rest/poll/Poll.kt | 8 ++++---- .../projectforge/rest/poll/PollPageRest.kt | 11 +++-------- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 0968ffdf01..4683044710 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -27,27 +27,26 @@ open class PollDO : DefaultBaseDO() { @get:Column(name = "description", nullable = true, length = 10000) open var description: String? = null - /* @PropertyInfo(i18nKey = "poll.owner") - @IndexedEmbedded(depth = 1) - @ManyToOne - @JoinColumn(name = "owner_pk") - open var owner: PFUserDO? = null*/ + @get:PropertyInfo(i18nKey = "poll.owner") + @get:ManyToOne(fetch = FetchType.LAZY) + @get:JoinColumn(name = "owner_pk") + open var owner: PFUserDO? = null @PropertyInfo(i18nKey = "poll.location") @get:Column(name = "location", nullable = true) open var location: String? = null -/* @PropertyInfo(i18nKey = "poll.date") - @get:Column(name = "date", nullable = true) - open var date: LocalDate? = null - @PropertyInfo(i18nKey = "poll.deadline") @get:Column(name = "deadline", nullable = true) open var deadline: LocalDate? = null + @PropertyInfo(i18nKey = "poll.date") + @get:Column(name = "date", nullable = true) + open var date: LocalDate? = null + @PropertyInfo(i18nKey = "poll.inputlist") @get:Column(name = "input_list", nullable = true, length = 10000) - open var inputFields: String? = null*/ + open var inputFields: String? = null /* @PropertyInfo(i18nKey = "poll.canSeeResultUsers") @get:Column(name = "canSeeResultUsers", nullable = true) diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql index b73c349350..3614f0c970 100644 --- a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql @@ -9,7 +9,11 @@ CREATE TABLE T_POLL title CHARACTER VARYING(1000), description CHARACTER VARYING(1000), location CHARACTER VARYING(1000), + owner_pk INTEGER NOT NULL, + deadline DATE NOT NULL ); ALTER TABLE T_POLL ADD CONSTRAINT t_poll_pkey PRIMARY KEY (pk); +ALTER TABLE T_POLL + ADD CONSTRAINT fk_t_poll_pf_user FOREIGN KEY (owner_pk) REFERENCES t_pf_user (pk); \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index b1d48b9547..2f909a628d 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -1,20 +1,20 @@ package org.projectforge.rest.poll +import net.sf.mpxj.LocaleData import org.projectforge.business.poll.PollDO import org.projectforge.framework.persistence.user.entities.PFUserDO import org.projectforge.rest.dto.BaseDTO import org.projectforge.ui.UIDataType +import java.time.LocalDate import java.util.* class Poll( var title: String? = null, var description: String? = null, -/* var owner: PFUserDO? = null, -*/ var location: String? = null, - var date: Date? = null, - var deadline: Date? = null, + var date: LocalDate? = null, + var deadline: LocalDate? = null, var inputFields: List? = null, var canSeeResultUsers: String? = null, var canEditPollUsers: String? = null, diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 2d280d00da..0e98f0d98d 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -2,12 +2,15 @@ package org.projectforge.rest.poll import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao +import org.projectforge.business.vacation.model.VacationDO import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType import org.projectforge.rest.VacationExportPageRest import org.projectforge.rest.config.Rest import org.projectforge.rest.core.* +import org.projectforge.rest.dto.PostData +import org.projectforge.rest.dto.Vacation import org.projectforge.ui.* import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping @@ -70,7 +73,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. "title", "description", "location", - "owner", "deadline" ) ) @@ -86,9 +88,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val title = dto.title val description = dto.description val location = dto.location -/* - val owner = dto.owner -*/ val deadline = dto.deadline updateStats(dto) @@ -108,9 +107,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val title = dto.title val description = dto.description val location = dto.location -/* - val owner = dto.owner -*/ val deadline = dto.deadline val pollDO = PollDO() @@ -120,7 +116,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. // PostMapping add @PostMapping("/add") fun abc(){ - } From 1bde3ce8bd9c32f0ebade0c4f51da5abc904c4bb Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Tue, 11 Apr 2023 17:28:51 +0200 Subject: [PATCH 013/160] begin to add Questions. It is Posible to add questions but not save, and not oly types --- .../org/projectforge/business/poll/PollDO.kt | 13 +- .../projectforge/business/poll/PollDao.java | 1 - .../V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql | 3 +- .../kotlin/org/projectforge/rest/poll/Poll.kt | 13 +- .../projectforge/rest/poll/PollPageRest.kt | 111 ++++++++++++++---- .../types/AntwortM\303\266glichkeiten.kt" | 9 -- .../rest/poll/types/FrageTypen.kt | 8 +- 7 files changed, 106 insertions(+), 52 deletions(-) delete mode 100644 "projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/AntwortM\303\266glichkeiten.kt" diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 4683044710..44d2183902 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -1,12 +1,10 @@ package org.projectforge.business.poll import org.hibernate.search.annotations.Indexed -import org.hibernate.search.annotations.IndexedEmbedded import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.persistence.api.AUserRightId import org.projectforge.framework.persistence.entities.DefaultBaseDO import org.projectforge.framework.persistence.user.entities.PFUserDO -import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.DependsOn import java.time.LocalDate import javax.persistence.* @@ -44,11 +42,11 @@ open class PollDO : DefaultBaseDO() { @get:Column(name = "date", nullable = true) open var date: LocalDate? = null - @PropertyInfo(i18nKey = "poll.inputlist") - @get:Column(name = "input_list", nullable = true, length = 10000) + @PropertyInfo(i18nKey = "poll.inputFields") + @get:Column(name = "inputFields", nullable = true, length = 1000) open var inputFields: String? = null - - /* @PropertyInfo(i18nKey = "poll.canSeeResultUsers") +/* + @PropertyInfo(i18nKey = "poll.canSeeResultUsers") @get:Column(name = "canSeeResultUsers", nullable = true) open var canSeeResultUsers: String? = null @@ -58,6 +56,7 @@ open class PollDO : DefaultBaseDO() { @PropertyInfo(i18nKey = "poll.canVoteInPoll") @get:Column(name = "canVoteInPoll", nullable = true) - open var canVoteInPoll: String? = null*/ + open var canVoteInPoll: String? = null + */ } \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java index 7320b06e5d..a06e67fce1 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java @@ -3,7 +3,6 @@ import org.projectforge.framework.access.*; import org.projectforge.framework.persistence.api.*; import org.projectforge.framework.persistence.user.entities.*; -import org.springframework.context.annotation.*; import org.springframework.stereotype.*; @Repository diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql index 3614f0c970..df6388238d 100644 --- a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql @@ -10,7 +10,8 @@ CREATE TABLE T_POLL description CHARACTER VARYING(1000), location CHARACTER VARYING(1000), owner_pk INTEGER NOT NULL, - deadline DATE NOT NULL + deadline DATE NOT NULL, + inputFields CHARACTER Varying(1000), ); ALTER TABLE T_POLL diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index 2f909a628d..9e0e1bbd3d 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -1,10 +1,10 @@ package org.projectforge.rest.poll -import net.sf.mpxj.LocaleData import org.projectforge.business.poll.PollDO import org.projectforge.framework.persistence.user.entities.PFUserDO import org.projectforge.rest.dto.BaseDTO -import org.projectforge.ui.UIDataType +import org.projectforge.rest.poll.types.BaseType +import org.projectforge.rest.poll.types.Frage import java.time.LocalDate import java.util.* @@ -15,14 +15,11 @@ class Poll( var location: String? = null, var date: LocalDate? = null, var deadline: LocalDate? = null, - var inputFields: List? = null, + var questionType: String? = null, + var inputFields: MutableList? = null, var canSeeResultUsers: String? = null, var canEditPollUsers: String? = null, var canVoteInPoll: String? = null ) : BaseDTO() { - class InputField( - var type: UIDataType? = null, - var name: String? = null, - var value: Any? = null, - ) + } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 0e98f0d98d..c0df983566 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -2,20 +2,21 @@ package org.projectforge.rest.poll import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao -import org.projectforge.business.vacation.model.VacationDO +import org.projectforge.framework.i18n.translate import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType import org.projectforge.rest.VacationExportPageRest import org.projectforge.rest.config.Rest -import org.projectforge.rest.core.* +import org.projectforge.rest.core.AbstractDTOPagesRest +import org.projectforge.rest.core.PagesResolver import org.projectforge.rest.dto.PostData -import org.projectforge.rest.dto.Vacation +import org.projectforge.rest.poll.types.BaseType +import org.projectforge.rest.poll.types.Frage import org.projectforge.ui.* import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* +import java.util.* import javax.servlet.http.HttpServletRequest @RestController @@ -34,7 +35,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return poll } - override fun createListLayout(request: HttpServletRequest, layout: UILayout, magicFilter: MagicFilter, userAccess: UILayout.UserAccess) { + override fun createListLayout( + request: HttpServletRequest, + layout: UILayout, + magicFilter: MagicFilter, + userAccess: UILayout.UserAccess + ) { agGridSupport.prepareUIGrid4ListPage( request, layout, @@ -55,6 +61,42 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) } + private fun addQuestionFieldset(dto: Poll): List{ + + val rows = mutableListOf() + dto.inputFields?.forEach { field -> + if(field.type == null){ + return rows + } + + var feld = UIRow() + if(field.type == BaseType.JaNeinFrage){ + feld.add( + UIFieldset(UILength(md = 6, lg = 4)) + .add(UIInput (field.uid+"question")) + .add(UICheckbox(field.uid+"antworten1", label = "Ja")) + .add(UICheckbox(field.uid+"antworten2", label = "Nein")))} + + if(field.type == BaseType.FreiTextFrage){ + feld.add(UIFieldset(UILength(md = 6, lg = 4)) + .add(UIInput (field.uid+"question")) + .add(UIInput(field.uid+"antworten") + ))} + + if(field.type == BaseType.MultipleChoices){ + feld.add(UIFieldset(UILength(md = 6, lg = 4)) + .add(lc, "question","antworten") + .add( UIButton.createAddButton( + responseAction = ResponseAction("${Rest.URL}/poll/addAntwortmöglichkeite", targetType = TargetType.POST) + ))) + } + + + rows.add(feld) + } + return rows + } + override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { val lc = LayoutContext(PollDO::class.java) val obj = PollDO() @@ -65,10 +107,27 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. .add( UIFieldset(UILength(md = 6, lg = 4)) .add(lc, "title", "description", "location", "owner", "deadline") + ) + ) + layout.add( + UIRow() + .add( + UIFieldset(UILength(md = 6, lg = 4)) + .add( + UIButton.createAddButton( + responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST) )) - .add(UIButton.createAddButton(responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST))) + .add( + UISelect( + "questionType", + values = BaseType.values().map {UISelectValue(it, it.name)} + ) + + ))) + addQuestionFieldset(dto).forEach(layout::add) + - layout.watchFields.addAll( + layout.watchFields.addAll( arrayOf( "title", "description", @@ -76,7 +135,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. "deadline" ) ) - updateStats(dto) return LayoutUtils.processEditPage(layout, dto, this) } @@ -90,7 +148,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val location = dto.location val deadline = dto.deadline - updateStats(dto) val userAccess = UILayout.UserAccess() val poll = PollDO() dto.copyTo(poll) @@ -102,22 +159,32 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) } - private fun updateStats(dto: Poll) { + // PostMapping add + @PostMapping("/add") + fun abc( + @RequestBody postData: PostData, + ): ResponseEntity { + val userAccess = UILayout.UserAccess(insert = true, update = true) + val dto = postData.data + var type = BaseType.valueOf(dto.questionType?:"FreiTextFrage") - val title = dto.title - val description = dto.description - val location = dto.location - val deadline = dto.deadline - val pollDO = PollDO() - dto.copyTo(pollDO) - } + val poll = PollDO() + if (dto.inputFields == null) { + dto.inputFields = mutableListOf() + } + dto.inputFields!!.add(Frage(uid = UUID.randomUUID().toString(), type = type)) - // PostMapping add - @PostMapping("/add") - fun abc(){ + dto.copyTo(poll) + return ResponseEntity.ok( + ResponseAction(targetType = TargetType.UPDATE) + .addVariable("data", dto) + .addVariable("ui", createEditLayout(dto, userAccess)) + ) } + // create a update layout funktion, welche das lyout nummr updatet und rurück gibt es soll für jeden Frage Basistyp eine eigene funktion haben + /*dto.inputFields?.forEachIndexed { field, index -> if (field.type == msc) { diff --git "a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/AntwortM\303\266glichkeiten.kt" "b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/AntwortM\303\266glichkeiten.kt" deleted file mode 100644 index 4fd8e0dc5e..0000000000 --- "a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/AntwortM\303\266glichkeiten.kt" +++ /dev/null @@ -1,9 +0,0 @@ -package org.projectforge.rest.poll.types - -class AntwortMöglichkeiten ( - id: String, - antwort: String - -){ - -} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/FrageTypen.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/FrageTypen.kt index 9ca1d3d083..56bd2d2b1f 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/FrageTypen.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/FrageTypen.kt @@ -3,10 +3,10 @@ package org.projectforge.rest.poll.types class Frage( val uid: String, - val question: String, - val type: BaseType, - var antworten: List, - var perrent: String? + val question: String? = "", + val type: BaseType = BaseType.FreiTextFrage, + var antworten: List = mutableListOf(), + var parent: String? = null, ){ } From 24744bd53e494dc8e521bc2a0fc06c50e7e8006c Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Wed, 12 Apr 2023 10:23:58 +0200 Subject: [PATCH 014/160] change PollDao from Java to Kotlin --- .../projectforge/business/poll/PollDao.java | 26 -------- .../org/projectforge/business/poll/PollDao.kt | 66 +++++++++++++++++++ 2 files changed, 66 insertions(+), 26 deletions(-) delete mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java create mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java deleted file mode 100644 index 7320b06e5d..0000000000 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.projectforge.business.poll; - -import org.projectforge.framework.access.*; -import org.projectforge.framework.persistence.api.*; -import org.projectforge.framework.persistence.user.entities.*; -import org.springframework.context.annotation.*; -import org.springframework.stereotype.*; - -@Repository -public class PollDao extends BaseDao { - - public PollDao() { - super(PollDO.class); - } - - @Override - public boolean hasAccess(PFUserDO user, PollDO obj, PollDO oldObj, OperationType operationType, boolean throwException) { - return true; - } - - @Override - public PollDO newInstance() { - return new PollDO(); - } - -} diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt new file mode 100644 index 0000000000..b383460d52 --- /dev/null +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -0,0 +1,66 @@ +package org.projectforge.business.poll + +import org.projectforge.business.user.service.UserService +import org.projectforge.framework.access.OperationType +import org.projectforge.framework.persistence.api.BaseDao +import org.projectforge.framework.persistence.api.QueryFilter +import org.projectforge.framework.persistence.api.impl.CustomResultFilter +import org.projectforge.framework.persistence.api.impl.DBPredicate +import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext +import org.projectforge.framework.persistence.user.entities.PFUserDO +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Repository + +@Repository +open class PollDao : BaseDao(PollDO::class.java){ + + @Autowired + private lateinit var userService: UserService + + override fun newInstance(): PollDO { + return PollDO() + } + + override fun hasAccess( + user: PFUserDO?, + obj: PollDO?, + oldObj: PollDO?, + operationType: OperationType?, + throwException: Boolean + ): Boolean { + return true + } + + /*override fun getList( + filter: QueryFilter, + customResultFilters: MutableList>? + ): List { + val loggedInUserId = ThreadLocalUserContext.userId + // Don't search for personal boxes of other users (they will be added afterwards): + filter.add( + DBPredicate.Or( + // Either not a personal box, + // or the personal box of the logged-in user: + DBPredicate.Equal("adminIds", loggedInUserId.toString()), + ) + ) + var result = super.getList(filter, customResultFilters) + // searchString contains trailing %: + val searchString = filter.fulltextSearchString?.replace("%", "") + if (searchString == null || searchString.length < 2) { // Search string is given and has at least 2 chars: + return result + } + result = result.toMutableList() + userService.sortedUsers.filter { user -> + user.username?.contains(searchString, ignoreCase = true) == true || + user.getFullname().contains(searchString, ignoreCase = true) + }.forEach { user -> + // User name matches given string, so add personal box of this active user: + result.add(internalLoadAll()[user.id]) + } + return result + } + + */ + +} \ No newline at end of file From 17c9ac5c64b6a2be2e9ce5d9a83ace2a51107b33 Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Wed, 12 Apr 2023 15:09:18 +0200 Subject: [PATCH 015/160] change data object and database --- .../org/projectforge/business/poll/PollDO.kt | 16 +++---- .../org/projectforge/business/poll/PollDao.kt | 42 ------------------- .../V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql | 3 +- 3 files changed, 11 insertions(+), 50 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 4683044710..daf982ff42 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -20,34 +20,36 @@ import javax.persistence.* open class PollDO : DefaultBaseDO() { @PropertyInfo(i18nKey = "poll.title") - @get:Column(name = "title", nullable = true, length = 1000) + @get:Column(name = "title", nullable = false, length = 1000) open var title: String? = null @PropertyInfo(i18nKey = "poll.description") - @get:Column(name = "description", nullable = true, length = 10000) + @get:Column(name = "description", length = 10000) open var description: String? = null @get:PropertyInfo(i18nKey = "poll.owner") @get:ManyToOne(fetch = FetchType.LAZY) - @get:JoinColumn(name = "owner_pk") + @get:JoinColumn(name = "owner_pk", nullable = false) open var owner: PFUserDO? = null @PropertyInfo(i18nKey = "poll.location") - @get:Column(name = "location", nullable = true) + @get:Column(name = "location") open var location: String? = null @PropertyInfo(i18nKey = "poll.deadline") - @get:Column(name = "deadline", nullable = true) + @get:Column(name = "deadline", nullable = false) open var deadline: LocalDate? = null @PropertyInfo(i18nKey = "poll.date") - @get:Column(name = "date", nullable = true) + @get:Column(name = "date") open var date: LocalDate? = null - @PropertyInfo(i18nKey = "poll.inputlist") + /*@PropertyInfo(i18nKey = "poll.inputlist") @get:Column(name = "input_list", nullable = true, length = 10000) open var inputFields: String? = null + */ + /* @PropertyInfo(i18nKey = "poll.canSeeResultUsers") @get:Column(name = "canSeeResultUsers", nullable = true) open var canSeeResultUsers: String? = null diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index b383460d52..549ee04617 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -1,22 +1,13 @@ package org.projectforge.business.poll -import org.projectforge.business.user.service.UserService import org.projectforge.framework.access.OperationType import org.projectforge.framework.persistence.api.BaseDao -import org.projectforge.framework.persistence.api.QueryFilter -import org.projectforge.framework.persistence.api.impl.CustomResultFilter -import org.projectforge.framework.persistence.api.impl.DBPredicate -import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.persistence.user.entities.PFUserDO -import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Repository @Repository open class PollDao : BaseDao(PollDO::class.java){ - @Autowired - private lateinit var userService: UserService - override fun newInstance(): PollDO { return PollDO() } @@ -30,37 +21,4 @@ open class PollDao : BaseDao(PollDO::class.java){ ): Boolean { return true } - - /*override fun getList( - filter: QueryFilter, - customResultFilters: MutableList>? - ): List { - val loggedInUserId = ThreadLocalUserContext.userId - // Don't search for personal boxes of other users (they will be added afterwards): - filter.add( - DBPredicate.Or( - // Either not a personal box, - // or the personal box of the logged-in user: - DBPredicate.Equal("adminIds", loggedInUserId.toString()), - ) - ) - var result = super.getList(filter, customResultFilters) - // searchString contains trailing %: - val searchString = filter.fulltextSearchString?.replace("%", "") - if (searchString == null || searchString.length < 2) { // Search string is given and has at least 2 chars: - return result - } - result = result.toMutableList() - userService.sortedUsers.filter { user -> - user.username?.contains(searchString, ignoreCase = true) == true || - user.getFullname().contains(searchString, ignoreCase = true) - }.forEach { user -> - // User name matches given string, so add personal box of this active user: - result.add(internalLoadAll()[user.id]) - } - return result - } - - */ - } \ No newline at end of file diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql index 3614f0c970..59f5fb7245 100644 --- a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql @@ -10,7 +10,8 @@ CREATE TABLE T_POLL description CHARACTER VARYING(1000), location CHARACTER VARYING(1000), owner_pk INTEGER NOT NULL, - deadline DATE NOT NULL + deadline DATE NOT NULL, + date DATE ); ALTER TABLE T_POLL From 047963647d8759934ad954dbfe5b7e6db1e5549d Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 14 Apr 2023 09:59:08 +0200 Subject: [PATCH 016/160] =?UTF-8?q?es=20ist=20jetzt=20m=C3=B6glich=20frage?= =?UTF-8?q?felder=20zu=20erstellen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../projectforge/rest/poll/PollPageRest.kt | 189 +++++++++++------- .../rest/poll/types/FrageTypen.kt | 18 +- 2 files changed, 126 insertions(+), 81 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index c0df983566..c646359457 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -2,7 +2,6 @@ package org.projectforge.rest.poll import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao -import org.projectforge.framework.i18n.translate import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType @@ -36,10 +35,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } override fun createListLayout( - request: HttpServletRequest, - layout: UILayout, - magicFilter: MagicFilter, - userAccess: UILayout.UserAccess + request: HttpServletRequest, layout: UILayout, magicFilter: MagicFilter, userAccess: UILayout.UserAccess ) { agGridSupport.prepareUIGrid4ListPage( request, @@ -47,10 +43,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. magicFilter, this, userAccess = userAccess, - ) - .add(lc, "title", "description", "location") - .add(lc, "owner") - .add(lc, "deadline") + ).add(lc, "title", "description", "location").add(lc, "owner").add(lc, "deadline") layout.add( MenuItem( "export", @@ -61,41 +54,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) } - private fun addQuestionFieldset(dto: Poll): List{ - - val rows = mutableListOf() - dto.inputFields?.forEach { field -> - if(field.type == null){ - return rows - } - var feld = UIRow() - if(field.type == BaseType.JaNeinFrage){ - feld.add( - UIFieldset(UILength(md = 6, lg = 4)) - .add(UIInput (field.uid+"question")) - .add(UICheckbox(field.uid+"antworten1", label = "Ja")) - .add(UICheckbox(field.uid+"antworten2", label = "Nein")))} - - if(field.type == BaseType.FreiTextFrage){ - feld.add(UIFieldset(UILength(md = 6, lg = 4)) - .add(UIInput (field.uid+"question")) - .add(UIInput(field.uid+"antworten") - ))} - - if(field.type == BaseType.MultipleChoices){ - feld.add(UIFieldset(UILength(md = 6, lg = 4)) - .add(lc, "question","antworten") - .add( UIButton.createAddButton( - responseAction = ResponseAction("${Rest.URL}/poll/addAntwortmöglichkeite", targetType = TargetType.POST) - ))) - } - - - rows.add(feld) - } - return rows - } override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { val lc = LayoutContext(PollDO::class.java) @@ -103,59 +62,64 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.copyTo(obj) val layout = super.createEditLayout(dto, userAccess) layout.add( - UIRow() - .add( - UIFieldset(UILength(md = 6, lg = 4)) - .add(lc, "title", "description", "location", "owner", "deadline") - ) + UIRow().add( + UIFieldset(UILength(md = 6, lg = 4)).add(lc, "title", "description", "location", "owner", "deadline") + ) ) layout.add( - UIRow() - .add( - UIFieldset(UILength(md = 6, lg = 4)) - .add( - UIButton.createAddButton( - responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST) - )) - .add( - UISelect( - "questionType", - values = BaseType.values().map {UISelectValue(it, it.name)} + UIRow().add( + UIFieldset(UILength(md = 6, lg = 4)).add( + UIButton.createAddButton( + responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST) ) + ).add( + UISelect("questionType", values = BaseType.values().map { UISelectValue(it, it.name) }) - ))) - addQuestionFieldset(dto).forEach(layout::add) + ) + ) + ) + addQuestionFieldset(layout, dto) layout.watchFields.addAll( arrayOf( - "title", - "description", - "location", - "deadline" + "title", "description", "location", "deadline" ) ) return LayoutUtils.processEditPage(layout, dto, this) } override fun onWatchFieldsUpdate( - request: HttpServletRequest, - dto: Poll, - watchFieldsTriggered: Array? + request: HttpServletRequest, dto: Poll, watchFieldsTriggered: Array? ): ResponseEntity { val title = dto.title val description = dto.description val location = dto.location val deadline = dto.deadline + val userAccess = UILayout.UserAccess() val poll = PollDO() dto.copyTo(poll) checkUserAccess(poll, userAccess) return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE) - .addVariable("data", dto) - .addVariable("ui", createEditLayout(dto, userAccess)) + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ) + } + + @PostMapping("/addAntwort/{fieldId}") + fun abc( + @RequestBody postData: PostData, + @PathVariable("fieldId") fieldUid: String, + ): ResponseEntity { + val dto = postData.data + val userAccess = UILayout.UserAccess(insert = true, update = true) + + val found = dto.inputFields?.find { it.uid == fieldUid } + found?.antworten?.add("") + + return ResponseEntity.ok( + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) } @@ -163,26 +127,97 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @PostMapping("/add") fun abc( @RequestBody postData: PostData, - ): ResponseEntity { + ): ResponseEntity { val userAccess = UILayout.UserAccess(insert = true, update = true) val dto = postData.data - var type = BaseType.valueOf(dto.questionType?:"FreiTextFrage") + var type = BaseType.valueOf(dto.questionType ?: "FreiTextFrage") val poll = PollDO() if (dto.inputFields == null) { dto.inputFields = mutableListOf() } - dto.inputFields!!.add(Frage(uid = UUID.randomUUID().toString(), type = type)) + + var frage = Frage(uid = UUID.randomUUID().toString(), type = type) + if(type== BaseType.JaNeinFrage) { + frage.antworten = mutableListOf("ja", "nein") + } + if(type== BaseType.DatumsAbfrage) { + frage.antworten = mutableListOf("Ja", "Vielleicht", "Nein") + } + + dto.inputFields!!.add(frage) dto.copyTo(poll) return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE) - .addVariable("data", dto) - .addVariable("ui", createEditLayout(dto, userAccess)) + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) } + + private fun addQuestionFieldset(layout: UILayout, dto: Poll) { + + dto.inputFields?.forEachIndexed { index, field -> + val feld = UIRow() + if (field.type == BaseType.JaNeinFrage) { + val groupLayout = UIGroup() + field.antworten?.forEach { antwort -> + groupLayout.add( + UIRadioButton( + "JaNeinRadio", antwort, label = antwort + ) + ) + } + feld.add( + UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add(UIInput("inputFields[${index}].question")).add + (groupLayout) + ) + } + + if (field.type == BaseType.FreiTextFrage) { + feld.add( + UIFieldset(UILength(md = 6, lg = 4)).add(UIInput("inputFields[${index}].question")) + ) + } + + if (field.type == BaseType.MultipleChoices || field.type == BaseType.DropDownFrage) { + val f = UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()) + .add(UIInput("inputFields[${index}].question", label = "Die Frage")) + field.antworten?.forEachIndexed { i, _ -> + f.add(UIInput("inputFields[${index}].antworten[${i}]", label = "AntwortMöglichkeit ${i + 1}")) + } + f.add( + UIButton.createAddButton( + responseAction = ResponseAction( + "${Rest.URL}/poll/addAntwort/${field.uid}", targetType = TargetType.POST + ) + ) + ) + if (field.type == BaseType.MultipleChoices) { + f.add( + UIInput( + "inputFields[${index}].numberOfSelect", dataType = UIDataType.INT, label = "Wie viele Sollen " + + "angeklickt werden können " + ) + ) + } + feld.add(f) + } + if (field.type == BaseType.DatumsAbfrage) { + feld.add( + UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add( + UIInput( + "inputFields[${index}].question", + label = "Hast du am ... Zeit?" + ) + ) + + ) + } + layout.add(feld) + } + } + // create a update layout funktion, welche das lyout nummr updatet und rurück gibt es soll für jeden Frage Basistyp eine eigene funktion haben diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/FrageTypen.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/FrageTypen.kt index 56bd2d2b1f..901398c0c5 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/FrageTypen.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/FrageTypen.kt @@ -1,15 +1,25 @@ package org.projectforge.rest.poll.types +import com.fasterxml.jackson.annotation.JsonIgnore +import org.projectforge.framework.time.PFDateTime +import org.projectforge.framework.time.PFDateTimeUtils +import org.projectforge.framework.time.PFDay +import org.projectforge.framework.time.PFDayUtils +import java.time.LocalDate +import java.time.LocalDateTime + class Frage( - val uid: String, + val uid: String?, val question: String? = "", val type: BaseType = BaseType.FreiTextFrage, - var antworten: List = mutableListOf(), + var antworten: MutableList? = mutableListOf(""), var parent: String? = null, -){ + var isRequired: Boolean? = false, + var numberOfSelect: Int? = 1, + +) -} enum class BaseType { JaNeinFrage, DatumsAbfrage, From 016688eb58bcd769d8b894052bf69ebb1f7e64cc Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 14 Apr 2023 15:06:14 +0200 Subject: [PATCH 017/160] add variable --- .../src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 7870dd37ec..3ba30aa5a1 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -205,7 +205,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. list.add(attachment) if(mail.isNotEmpty()){ - pollMailService.sendMail("Die Umfrage ","Sehr geehrter Teilnehmer,wir laden Sie herzlich dazu ein, an unserer Umfrage zum Thema [Titel der Umfrage] teilzunehmen. Ihre Meinung ist uns sehr wichtig und wir würden uns freuen, wenn Sie uns dabei helfen könnten, unsere Forschungsergebnisse zu verbessern.Für diese Umfrage ist [Name des Ansprechpartners] zuständig. Bei Fragen oder Anmerkungen können Sie sich gerne an ihn wenden.Bitte beachten Sie, dass das Enddatum für die Teilnahme an dieser Umfrage der [Enddatum] ist. Wir würden uns freuen, wenn Sie sich die Zeit nehmen könnten, um diese Umfrage auszufüllen.Vielen Dank im Voraus für Ihre Unterstützung.Mit freundlichen Grüßen,[Name des Absenders]",list) + pollMailService.sendMail(header,mail,list) } } From b7151267718f98793d217727367382eef435812b Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 14 Apr 2023 15:06:38 +0200 Subject: [PATCH 018/160] add variable --- .../main/kotlin/org/projectforge/rest/poll/PollPageRest.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 3ba30aa5a1..24a81f0214 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -140,6 +140,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) } + override fun onAfterSaveOrUpdate(request: HttpServletRequest, obj: PollDO, postData: PostData) { + super.onAfterSaveOrUpdate(request, obj, postData) + se + } + @PostMapping("/addAntwort/{fieldId}") fun addAntwort( @RequestBody postData: PostData, From 07ab62789ffd39731102d4dd1e1bde38fbcc13de Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 14 Apr 2023 15:34:23 +0200 Subject: [PATCH 019/160] add on after create ore update send a mail. --- .../main/kotlin/org/projectforge/business/poll/PollDO.kt | 6 +++--- .../kotlin/org/projectforge/rest/poll/PollMailService.kt | 9 +++++---- .../kotlin/org/projectforge/rest/poll/PollPageRest.kt | 5 +++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 6707e241c6..9ade447dcd 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -38,9 +38,9 @@ open class PollDO : DefaultBaseDO() { @get:Column(name = "deadline", nullable = false) open var deadline: LocalDate? = null - @PropertyInfo(i18nKey = "poll.date") - @get:Column(name = "date") - open var date: LocalDate? = null + @PropertyInfo(i18nKey = "poll.date") + @get:Column(name = "date") + open var date: LocalDate? = null @PropertyInfo(i18nKey = "poll.inputFields") @get:Column(name = "inputFields", nullable = true, length = 1000) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt index e958bcf955..413d9e71dc 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt @@ -13,17 +13,18 @@ class PollMailService { private lateinit var sendMail: SendMail - fun sendMail(subject: String, content: String, mailAttachments: List?) { - - val address ="j.bernst@micrmomata.de" + fun sendMail(to:String ,subject: String,content: String, mailAttachments: List?= null){ val mail = Mail() mail.subject = subject mail.contentType = Mail.CONTENTTYPE_HTML - mail.setTo(address) + mail.setTo(to) mail.content = content sendMail.send(mail, attachments = mailAttachments) } + + + } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 24a81f0214..2823452b79 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -142,7 +142,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun onAfterSaveOrUpdate(request: HttpServletRequest, obj: PollDO, postData: PostData) { super.onAfterSaveOrUpdate(request, obj, postData) - se + val dto = postData.data + pollMailService.sendMail(subject = "", content = "", to = "test.mail") } @PostMapping("/addAntwort/{fieldId}") @@ -210,7 +211,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. list.add(attachment) if(mail.isNotEmpty()){ - pollMailService.sendMail(header,mail,list) + pollMailService.sendMail(to="test", subject = header, content = mail, mailAttachments = list) } } From 0f065660653f4d3ec8b882afcd8c6a9af3300565 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Sun, 16 Apr 2023 22:30:23 +0200 Subject: [PATCH 020/160] git add that poll is in getExcel --- .../kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt | 3 ++- .../main/kotlin/org/projectforge/rest/poll/PollPageRest.kt | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt index 49bdc5ebcb..70c235f66a 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt @@ -3,6 +3,7 @@ package org.projectforge.rest.poll.Exel import de.micromata.merlin.excel.ExcelRow import de.micromata.merlin.excel.ExcelSheet import de.micromata.merlin.excel.ExcelWorkbook +import org.projectforge.rest.poll.Poll import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.core.io.ClassPathResource @@ -18,7 +19,7 @@ class ExcelExport { private val FIRST_DATA_ROW_NUM = 5 - fun getExcel(): ByteArray? { + fun getExcel(obj: Poll): ByteArray? { //var excelSheet: ExcelSheet? = null //var emptyRow: ExcelRow? = null diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 1821842202..b0b8415202 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -235,11 +235,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } } + @PostMapping("Export") - fun export(request: HttpServletRequest) : ResponseEntity? { + fun export(request: HttpServletRequest,poll: Poll) : ResponseEntity? { val ihkExporter = ExcelExport() val bytes: ByteArray? = ihkExporter - .getExcel() + .getExcel(poll) val filename = ("test.xlsx") if (bytes == null || bytes.size == 0) { From 77982c1c32e40f9666ef759c0e330e6fd1a8c2a8 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Mon, 17 Apr 2023 09:54:27 +0200 Subject: [PATCH 021/160] update title --- .../src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 81726428e2..52ae4abfe2 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -185,7 +185,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. if (field.type == BaseType.FreiTextFrage) { feld.add( - UIFieldset(UILength(md = 6, lg = 4)).add(UIInput("inputFields[${index}].question")) + UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add(UIInput("inputFields[${index}].question")) ) } From cadd3aab18797331585239fa9855d6c15a4c91d2 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Mon, 17 Apr 2023 10:16:02 +0200 Subject: [PATCH 022/160] refactore PR --- .../projectforge/rest/poll/AntwortSeite.kt | 35 +++++++++++++++++++ .../projectforge/rest/poll/PollPageRest.kt | 6 ++-- 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt new file mode 100644 index 0000000000..74d0c15ce1 --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt @@ -0,0 +1,35 @@ +package org.projectforge.rest.poll + +import org.projectforge.business.poll.PollDO +import org.projectforge.business.poll.PollDao +import org.projectforge.framework.persistence.api.MagicFilter +import org.projectforge.rest.config.Rest +import org.projectforge.rest.core.AbstractDTOPagesRest +import org.projectforge.ui.UILayout +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import javax.servlet.http.HttpServletRequest + + +@RestController +@RequestMapping("${Rest.URL}/poll/antwort") +class AntwortSeite : AbstractDTOPagesRest(PollDao::class.java, "poll.title") { + override fun createListLayout( + request: HttpServletRequest, + layout: UILayout, + magicFilter: MagicFilter, + userAccess: UILayout.UserAccess + ) { + TODO("Not yet implemented") + } + + override fun transformFromDB(obj: PollDO, editMode: Boolean): Poll { + TODO("Not yet implemented") + } + + override fun transformForDB(dto: Poll): PollDO { + TODO("Not yet implemented") + } + + +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 52ae4abfe2..ba5cf6e739 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -33,6 +33,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return pollDO } + + //override fun transformForDB editMode not used override fun transformFromDB(obj: PollDO, editMode: Boolean): Poll { val poll = Poll() poll.copyFrom(obj) @@ -117,7 +119,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } @PostMapping("/addAntwort/{fieldId}") - fun abc( + fun addAntwortFeld( @RequestBody postData: PostData, @PathVariable("fieldId") fieldUid: String, ): ResponseEntity { @@ -134,7 +136,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. // PostMapping add @PostMapping("/add") - fun abc( + fun addFrageFeld( @RequestBody postData: PostData, ): ResponseEntity { val userAccess = UILayout.UserAccess(insert = true, update = true) From cba5b08d522b75cda634eadacae1a20faa03daea Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Mon, 17 Apr 2023 13:15:36 +0200 Subject: [PATCH 023/160] init dynamic page --- .../projectforge/rest/poll/AntwortSeite.kt | 35 --------- .../rest/poll/ResponsePageRest.kt | 78 +++++++++++++++++++ .../rest/poll/types/PollResponse.kt | 4 + .../rest/poll/types/PollResponseDO.kt | 4 + 4 files changed, 86 insertions(+), 35 deletions(-) delete mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponseDO.kt diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt deleted file mode 100644 index 74d0c15ce1..0000000000 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.projectforge.rest.poll - -import org.projectforge.business.poll.PollDO -import org.projectforge.business.poll.PollDao -import org.projectforge.framework.persistence.api.MagicFilter -import org.projectforge.rest.config.Rest -import org.projectforge.rest.core.AbstractDTOPagesRest -import org.projectforge.ui.UILayout -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController -import javax.servlet.http.HttpServletRequest - - -@RestController -@RequestMapping("${Rest.URL}/poll/antwort") -class AntwortSeite : AbstractDTOPagesRest(PollDao::class.java, "poll.title") { - override fun createListLayout( - request: HttpServletRequest, - layout: UILayout, - magicFilter: MagicFilter, - userAccess: UILayout.UserAccess - ) { - TODO("Not yet implemented") - } - - override fun transformFromDB(obj: PollDO, editMode: Boolean): Poll { - TODO("Not yet implemented") - } - - override fun transformForDB(dto: Poll): PollDO { - TODO("Not yet implemented") - } - - -} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt new file mode 100644 index 0000000000..189a44e0be --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -0,0 +1,78 @@ +package org.projectforge.rest.poll + +import org.projectforge.business.poll.PollDO +import org.projectforge.business.poll.PollDao +import org.projectforge.rest.config.Rest +import org.projectforge.rest.core.AbstractDynamicPageRest +import org.projectforge.rest.dto.FormLayoutData +import org.projectforge.rest.poll.types.BaseType +import org.projectforge.ui.* +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController +import javax.servlet.http.HttpServletRequest + + +@RestController +@RequestMapping("${Rest.URL}/poll/antwort") +class ResponsePageRest : AbstractDynamicPageRest() { + + + + + @GetMapping("dynamic") + fun test(request: HttpServletRequest, @RequestParam("pollId") pollId: Int?): FormLayoutData { + + val layout = UILayout("poll.antwort.title") + val lc = LayoutContext(PollDO::class.java) + val data = PollDao().getById(1) + val dto = Poll() + dto.copyFrom(data) + + + layout.add( + UIRow().add( + UIFieldset(UILength(md = 6, lg = 4)).add(lc, "title", "description", "location", "owner", "deadline") + ) + ) + addQuestions(layout, dto) + + return FormLayoutData(data, layout, createServerData(request)) + } + + private fun addQuestions(layout: UILayout, dto: Poll) { + + + + dto.inputFields?.forEachIndexed { index, field -> + val feld = UIRow() + feld.add(UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString())) + .add(UICol(6).add(UILabel(field.question))) + + if (field.type == BaseType.FreiTextFrage) { + feld.add(UIInput("antwort",)) + } + if (field.type == BaseType.JaNeinFrage) { + feld.add(UICheckbox("antwort",)) + feld.add(UICheckbox("antwort",)) + } + if (field.type == BaseType.DatumsAbfrage){ + + feld.add(UICheckbox("antwort",)) + feld.add(UICheckbox("antwort",)) + feld.add(UICheckbox("antwort",)) + } + if (field.type == BaseType.DropDownFrage){ + feld.add(UISelect("questionType", values = field.antworten?.map { UISelectValue(it,it) })) + } + if( field.type == BaseType.MultipleChoices){ + field.antworten?.forEachIndexed{ index, s -> + feld.add(UICheckbox("antwort${index}", label =field.antworten?.get(index) ?: "")) + } + } + layout.add(feld) + } + } + +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt new file mode 100644 index 0000000000..f8ef6ae33d --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt @@ -0,0 +1,4 @@ +package org.projectforge.rest.poll.types + +class PollResponse { +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponseDO.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponseDO.kt new file mode 100644 index 0000000000..6f417fb378 --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponseDO.kt @@ -0,0 +1,4 @@ +package org.projectforge.rest.poll.types + +class PollResponseDO { +} \ No newline at end of file From f8ded31e8004ef67cc352743bf51a2cf868bc745 Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Mon, 17 Apr 2023 13:16:41 +0200 Subject: [PATCH 024/160] Add filter for overview Page --- .../business/poll/PollAssignment.kt | 10 ++++ .../business/poll/PollAssignmentFilter.kt | 10 ++++ .../org/projectforge/business/poll/PollDO.kt | 24 +++++++++- .../projectforge/business/poll/PollStatus.kt | 10 ++++ .../business/poll/PollStatusFilter.kt | 10 ++++ .../projectforge/rest/poll/PollPageRest.kt | 46 +++++++++++++++++-- 6 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignment.kt create mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignmentFilter.kt create mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatus.kt create mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatusFilter.kt diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignment.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignment.kt new file mode 100644 index 0000000000..d3929d5262 --- /dev/null +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignment.kt @@ -0,0 +1,10 @@ +package org.projectforge.business.poll + +import org.projectforge.common.i18n.I18nEnum + +enum class PollAssignment(val key: String): I18nEnum { + OWNER("owner"), ACCESS("access"), ATTENDEE("attendee"), OTHER("other"); + + override val i18nKey: String + get() = ("pollAssignment.$key") +} \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignmentFilter.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignmentFilter.kt new file mode 100644 index 0000000000..2a11ce4bd6 --- /dev/null +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignmentFilter.kt @@ -0,0 +1,10 @@ +package org.projectforge.business.poll + +import org.projectforge.framework.persistence.api.impl.CustomResultFilter + +class PollAssignmentFilter(val values: List): CustomResultFilter { + + override fun match(list: MutableList, element: PollDO): Boolean { + return values.contains(element.getPollAssignment()) + } +} \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index daf982ff42..1d05cd8f1a 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -1,12 +1,11 @@ package org.projectforge.business.poll import org.hibernate.search.annotations.Indexed -import org.hibernate.search.annotations.IndexedEmbedded import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.persistence.api.AUserRightId import org.projectforge.framework.persistence.entities.DefaultBaseDO +import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.persistence.user.entities.PFUserDO -import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.DependsOn import java.time.LocalDate import javax.persistence.* @@ -44,6 +43,27 @@ open class PollDO : DefaultBaseDO() { @get:Column(name = "date") open var date: LocalDate? = null + @Transient + fun getPollAssignment(): PollAssignment { + val currentUserId = ThreadLocalUserContext.userId + val owner = if (owner != null) owner!!.id else null + return if (currentUserId == owner) { + PollAssignment.OWNER + } else { + PollAssignment.OTHER + } + } + + @Transient + fun getPollStatus(): PollStatus { + return if (LocalDate.now().isAfter(deadline)) { + PollStatus.EXPIRED + } else { + PollStatus.ACTIVE + } + } + + /*@PropertyInfo(i18nKey = "poll.inputlist") @get:Column(name = "input_list", nullable = true, length = 10000) open var inputFields: String? = null diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatus.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatus.kt new file mode 100644 index 0000000000..dd53fd7521 --- /dev/null +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatus.kt @@ -0,0 +1,10 @@ +package org.projectforge.business.poll + +import org.projectforge.common.i18n.I18nEnum + +enum class PollStatus(val key: String): I18nEnum { + ACTIVE("active"), EXPIRED("expired"); + + override val i18nKey: String + get() = "poll.$key" +} \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatusFilter.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatusFilter.kt new file mode 100644 index 0000000000..5a0a3052f4 --- /dev/null +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatusFilter.kt @@ -0,0 +1,10 @@ +package org.projectforge.business.poll + +import org.projectforge.framework.persistence.api.impl.CustomResultFilter + +class PollStatusFilter(val values: List): CustomResultFilter { + + override fun match(list: MutableList, element: PollDO): Boolean { + return values.contains(element.getPollStatus()) + } +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 0e98f0d98d..4ddd934156 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -1,17 +1,21 @@ package org.projectforge.rest.poll -import org.projectforge.business.poll.PollDO -import org.projectforge.business.poll.PollDao +import org.projectforge.business.poll.* import org.projectforge.business.vacation.model.VacationDO +import org.projectforge.business.vacation.model.VacationMode +import org.projectforge.business.vacation.model.VacationModeFilter +import org.projectforge.business.vacation.model.VacationStatus +import org.projectforge.framework.i18n.translate import org.projectforge.framework.persistence.api.MagicFilter +import org.projectforge.framework.persistence.api.QueryFilter +import org.projectforge.framework.persistence.api.impl.CustomResultFilter import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType import org.projectforge.rest.VacationExportPageRest import org.projectforge.rest.config.Rest import org.projectforge.rest.core.* -import org.projectforge.rest.dto.PostData -import org.projectforge.rest.dto.Vacation import org.projectforge.ui.* +import org.projectforge.ui.filter.UIFilterListElement import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestMapping @@ -80,6 +84,40 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return LayoutUtils.processEditPage(layout, dto, this) } + override fun addMagicFilterElements(elements: MutableList) { + elements.add( + UIFilterListElement("assignment", label = translate("poll.pollAssignment"), defaultFilter = true) + .buildValues(PollAssignment.OWNER, PollAssignment.OTHER) + ) + elements.add( + UIFilterListElement("status", label = translate("poll.status"), defaultFilter = true) + .buildValues(PollStatus.ACTIVE, PollStatus.EXPIRED) + ) + } + + override fun preProcessMagicFilter(target: QueryFilter, source: MagicFilter): List>? { + val filters = mutableListOf>() + val assignmentFilterEntry = source.entries.find { it.field == "assignment" } + if (assignmentFilterEntry != null) { + assignmentFilterEntry.synthetic = true + val values = assignmentFilterEntry.value.values + if (!values.isNullOrEmpty()) { + val enums = values.map { PollAssignment.valueOf(it) } + filters.add(PollAssignmentFilter(enums)) + } + } + val statusFilterEntry = source.entries.find { it.field == "status" } + if (statusFilterEntry != null) { + statusFilterEntry.synthetic = true + val values = statusFilterEntry.value.values + if (!values.isNullOrEmpty()) { + val enums = values.map { PollStatus.valueOf(it) } + filters.add(PollStatusFilter(enums)) + } + } + return filters + } + override fun onWatchFieldsUpdate( request: HttpServletRequest, dto: Poll, From 96a0e548cc39e80cefb4f4b319b40530eb8b6bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Mon, 17 Apr 2023 14:27:10 +0200 Subject: [PATCH 025/160] Created State for polls --- .../org/projectforge/business/poll/PollDO.kt | 16 +++++++++++---- .../V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql | 8 +++++++- .../kotlin/org/projectforge/rest/poll/Poll.kt | 3 +-- .../projectforge/rest/poll/PollPageRest.kt | 20 +++++++++++-------- .../org/projectforge/rest/poll/State.kt | 5 +++++ 5 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/State.kt diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index daf982ff42..91501adcfc 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -1,12 +1,10 @@ package org.projectforge.business.poll import org.hibernate.search.annotations.Indexed -import org.hibernate.search.annotations.IndexedEmbedded import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.persistence.api.AUserRightId import org.projectforge.framework.persistence.entities.DefaultBaseDO import org.projectforge.framework.persistence.user.entities.PFUserDO -import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.DependsOn import java.time.LocalDate import javax.persistence.* @@ -50,7 +48,16 @@ open class PollDO : DefaultBaseDO() { */ - /* @PropertyInfo(i18nKey = "poll.canSeeResultUsers") + enum class State { + RUNNING, FINISHED + } + + @PropertyInfo(i18nKey = "poll.state") + @get:Column(name = "state", nullable = false) + open var state: State? = State.RUNNING + +/* + @PropertyInfo(i18nKey = "poll.canSeeResultUsers") @get:Column(name = "canSeeResultUsers", nullable = true) open var canSeeResultUsers: String? = null @@ -60,6 +67,7 @@ open class PollDO : DefaultBaseDO() { @PropertyInfo(i18nKey = "poll.canVoteInPoll") @get:Column(name = "canVoteInPoll", nullable = true) - open var canVoteInPoll: String? = null*/ + open var canVoteInPoll: String? = null +*/ } \ No newline at end of file diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql index 59f5fb7245..d78f87dc07 100644 --- a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql @@ -11,7 +11,13 @@ CREATE TABLE T_POLL location CHARACTER VARYING(1000), owner_pk INTEGER NOT NULL, deadline DATE NOT NULL, - date DATE + date DATE, + state CHARACTER VARYING(1000), +/* + canSeeResultUsers CHARACTER VARYING(1000), + canEditPollUsers CHARACTER VARYING(1000), + canVoteInPoll CHARACTER VARYING(1000), +*/ ); ALTER TABLE T_POLL diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index 2f909a628d..a813b82096 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -1,12 +1,10 @@ package org.projectforge.rest.poll -import net.sf.mpxj.LocaleData import org.projectforge.business.poll.PollDO import org.projectforge.framework.persistence.user.entities.PFUserDO import org.projectforge.rest.dto.BaseDTO import org.projectforge.ui.UIDataType import java.time.LocalDate -import java.util.* class Poll( var title: String? = null, @@ -16,6 +14,7 @@ class Poll( var date: LocalDate? = null, var deadline: LocalDate? = null, var inputFields: List? = null, + var state: State? = State.RUNNING, var canSeeResultUsers: String? = null, var canEditPollUsers: String? = null, var canVoteInPoll: String? = null diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 0e98f0d98d..e226be1266 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -2,15 +2,12 @@ package org.projectforge.rest.poll import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao -import org.projectforge.business.vacation.model.VacationDO import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType import org.projectforge.rest.VacationExportPageRest import org.projectforge.rest.config.Rest import org.projectforge.rest.core.* -import org.projectforge.rest.dto.PostData -import org.projectforge.rest.dto.Vacation import org.projectforge.ui.* import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping @@ -35,6 +32,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } override fun createListLayout(request: HttpServletRequest, layout: UILayout, magicFilter: MagicFilter, userAccess: UILayout.UserAccess) { + var id = 10094 agGridSupport.prepareUIGrid4ListPage( request, layout, @@ -42,9 +40,13 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. this, userAccess = userAccess, ) - .add(lc, "title", "description", "location") - .add(lc, "owner") - .add(lc, "deadline") + .add(lc, "title", "description", "location", "owner", "deadline") + .add(lc, "canSeeResultUsers") + .add(lc, "canEditPollUsers") + .add(lc, "canVoteInPoll") + .add(lc,"button") + + layout.add( MenuItem( "export", @@ -53,6 +55,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. type = MenuItemTargetType.REDIRECT, ) ) + layout.add(UIButton.createAddButton(responseAction = ResponseAction("${Rest.URL}/poll/edit?id=${id}", targetType = TargetType.GET))) + } override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { @@ -68,7 +72,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. )) .add(UIButton.createAddButton(responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST))) - layout.watchFields.addAll( + layout.watchFields.addAll( arrayOf( "title", "description", @@ -115,7 +119,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. // PostMapping add @PostMapping("/add") - fun abc(){ + fun add(){ } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/State.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/State.kt new file mode 100644 index 0000000000..b3e088a361 --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/State.kt @@ -0,0 +1,5 @@ +package org.projectforge.rest.poll + +public enum class State { + RUNNING, FINISHED +} From 94f5bd769c77249be727c6292f67d3ef8f17cd89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Mon, 17 Apr 2023 16:01:10 +0200 Subject: [PATCH 026/160] Added cronjob to set State.FINISHED --- .../kotlin/org/projectforge/rest/poll/Poll.kt | 2 +- .../projectforge/rest/poll/PollPageRest.kt | 96 +++++++++++-------- .../org/projectforge/rest/poll/State.kt | 5 - 3 files changed, 59 insertions(+), 44 deletions(-) delete mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/State.kt diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index b36859dc8a..d1ef25fe1e 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -13,7 +13,7 @@ class Poll( var location: String? = null, var date: LocalDate? = null, var deadline: LocalDate? = null, - var state: State? = State.RUNNING, + var state: PollDO.State? = PollDO.State.RUNNING, var questionType: String? = null, var inputFields: MutableList? = null, var canSeeResultUsers: String? = null, diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 7a626066d6..90f8c8e34e 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -79,11 +79,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. this, userAccess = userAccess, ) - .add(lc, "title", "description", "location", "owner", "deadline") - .add(lc, "canSeeResultUsers") - .add(lc, "canEditPollUsers") - .add(lc, "canVoteInPoll") - .add(lc,"button") + .add(lc, "title", "description", "location", "owner", "deadline", "state") layout.add( @@ -99,7 +95,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } - override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { val lc = LayoutContext(PollDO::class.java) val obj = PollDO() @@ -124,7 +119,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) addQuestionFieldset(layout, dto) - layout.watchFields.addAll( arrayOf( "title", "description", "location", "deadline" @@ -172,64 +166,90 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) } - @Scheduled(fixedRate = 50000) - fun cronJobSch() { - // check if poll end in Future + /** + * Method to end polls after deadline + */ + fun cronEndPolls() { val polls = pollDao.internalLoadAll() - var mail = ""; - var header = ""; - // erstell mir eine funktion, die alles deadlines mir gibt die in der zukunft liegen - val pollsInFuture = polls.filter { it.deadline?.isAfter(LocalDate.now()) ?: false} - pollsInFuture.forEach{ - val daysDifference = ChronoUnit.DAYS.between(LocalDate.now(), it.deadline) - if(daysDifference == 1L || daysDifference == 7L){ - header = "Umfrage Endet in $daysDifference Tage" - mail =""" + + // set State.FINISHED for all old polls + polls.forEach { + if (it.deadline?.isBefore(LocalDate.now()) == true) { + it.state = PollDO.State.FINISHED + pollDao.internalSaveOrUpdate(it) + } + } + + try { + var mail = ""; + var header = ""; + // erstell mir eine funktion, die alles deadlines mir gibt die in der zukunft liegen + val pollsInFuture = polls.filter { it.deadline?.isAfter(LocalDate.now()) ?: false} + pollsInFuture.forEach{ + val daysDifference = ChronoUnit.DAYS.between(LocalDate.now(), it.deadline) + if(daysDifference == 1L || daysDifference == 7L){ + header = "Umfrage Endet in $daysDifference Tage" + mail =""" Sehr geehrter Teilnehmer,wir laden Sie herzlich dazu ein, an unserer Umfrage zum Thema ${it.title} teilzunehmen. Ihre Meinung ist uns sehr wichtig und wir würden uns freuen, wenn Sie uns dabei helfen könnten, unsere Forschungsergebnisse zu verbessern. Für diese Umfrage ist ${it ///owmer - }zuständig. + }zuständig. Bei Fragen oder Anmerkungen können Sie sich gerne an ihn wenden. Bitte beachten Sie, dass das Enddatum für die Teilnahme an dieser Umfrage der ${it.deadline.toString()} ist. Wir würden uns freuen, wenn Sie sich die Zeit nehmen könnten, um diese Umfrage auszufüllen. Vielen Dank im Voraus für Ihre Unterstützung. Mit freundlichen Grüßen,${it ///owmer - } + } """.trimMargin() + } } - } - // check if state ist open or closed - val list = ArrayList() + // check if state is open or closed + val list = ArrayList() + + val ihkExporter = ExcelExport() + // val exel = ihkExporter + // .getExcel() - val ihkExporter = ExcelExport() - // val exel = ihkExporter - // .getExcel() + val attachment = object : MailAttachment { + override fun getFilename(): String { + return "test"+ "_" + LocalDateTime.now().year + ".xlsx" + } - val attachment = object : MailAttachment { - override fun getFilename(): String { - return "test"+ "_" + LocalDateTime.now().year + ".xlsx" + override fun getContent(): ByteArray? { + // return exel + return null + } } + list.add(attachment) - override fun getContent(): ByteArray? { - // return exel - return null + if(mail.isNotEmpty()){ + pollMailService.sendMail(to="test", subject = header, content = mail, mailAttachments = list) } } - list.add(attachment) - - if(mail.isNotEmpty()){ - pollMailService.sendMail(to="test", subject = header, content = mail, mailAttachments = list) + catch (e:Exception) { + log.error(e.toString()) } } + /** + * Cron job for daily stuff + */ + //@Scheduled(cron = "0 0 1 * * *") // 1am everyday + @Scheduled(cron = "0 * * * * *") // 1am everyday + fun dailyCronJobs() { + cronDeletePolls() + cronEndPolls() + } - @Scheduled(fixedRate = 50000) //cron = "0 0 1 * * *" // 1am + /** + * Method to delete old polls + */ fun cronDeletePolls() { // check if poll end in Future val polls = pollDao.internalLoadAll() diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/State.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/State.kt deleted file mode 100644 index b3e088a361..0000000000 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/State.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.projectforge.rest.poll - -public enum class State { - RUNNING, FINISHED -} From be1f00a8f9a22ddaca4084b6004fc1e791377491 Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Mon, 17 Apr 2023 17:05:11 +0200 Subject: [PATCH 027/160] accessuser and accessgroup --- .../org/projectforge/business/poll/PollDO.kt | 25 +------- .../projectforge/business/poll/PollDao.java | 26 --------- .../org/projectforge/business/poll/PollDao.kt | 47 +++++++++++++++ ...L_TABLE.sql => V7.5.1.2__7.5.1.0-POLL.sql} | 3 +- .../migrate/common/V7.5.1.4__7.5.1.0-POLL.sql | 7 +++ .../migrate/common/V7.5.1.5__7.5.1.0-POLL.sql | 7 +++ .../migrate/common/V7.5.1.6__7.5.1.0-POLL.sql | 4 ++ .../migrate/common/V7.5.1.7__7.5.1.0-POLL.sql | 2 + .../kotlin/org/projectforge/rest/poll/Poll.kt | 30 +++++----- .../projectforge/rest/poll/PollPageRest.kt | 58 +++++++++++++++---- 10 files changed, 135 insertions(+), 74 deletions(-) delete mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java create mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt rename projectforge-business/src/main/resources/flyway/migrate/common/{V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql => V7.5.1.2__7.5.1.0-POLL.sql} (89%) create mode 100644 projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.4__7.5.1.0-POLL.sql create mode 100644 projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.5__7.5.1.0-POLL.sql create mode 100644 projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.6__7.5.1.0-POLL.sql create mode 100644 projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.7__7.5.1.0-POLL.sql diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 4683044710..066646503c 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -1,12 +1,10 @@ package org.projectforge.business.poll import org.hibernate.search.annotations.Indexed -import org.hibernate.search.annotations.IndexedEmbedded +import org.projectforge.business.common.BaseUserGroupRightsDO import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.persistence.api.AUserRightId -import org.projectforge.framework.persistence.entities.DefaultBaseDO import org.projectforge.framework.persistence.user.entities.PFUserDO -import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.DependsOn import java.time.LocalDate import javax.persistence.* @@ -17,7 +15,7 @@ import javax.persistence.* @Table(name = "t_poll") @AUserRightId(value = "poll", checkAccess = false) @DependsOn("org.projectforge.framework.persistence.user.entities.PFUserDO") -open class PollDO : DefaultBaseDO() { +open class PollDO : BaseUserGroupRightsDO() { @PropertyInfo(i18nKey = "poll.title") @get:Column(name = "title", nullable = true, length = 1000) @@ -30,7 +28,7 @@ open class PollDO : DefaultBaseDO() { @get:PropertyInfo(i18nKey = "poll.owner") @get:ManyToOne(fetch = FetchType.LAZY) @get:JoinColumn(name = "owner_pk") - open var owner: PFUserDO? = null + override var owner: PFUserDO? = null @PropertyInfo(i18nKey = "poll.location") @get:Column(name = "location", nullable = true) @@ -43,21 +41,4 @@ open class PollDO : DefaultBaseDO() { @PropertyInfo(i18nKey = "poll.date") @get:Column(name = "date", nullable = true) open var date: LocalDate? = null - - @PropertyInfo(i18nKey = "poll.inputlist") - @get:Column(name = "input_list", nullable = true, length = 10000) - open var inputFields: String? = null - - /* @PropertyInfo(i18nKey = "poll.canSeeResultUsers") - @get:Column(name = "canSeeResultUsers", nullable = true) - open var canSeeResultUsers: String? = null - - @PropertyInfo(i18nKey = "poll.canEditPollUsers") - @get:Column(name = "canEditPollUsers", nullable = true) - open var canEditPollUsers: String? = null - - @PropertyInfo(i18nKey = "poll.canVoteInPoll") - @get:Column(name = "canVoteInPoll", nullable = true) - open var canVoteInPoll: String? = null*/ - } \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java deleted file mode 100644 index 7320b06e5d..0000000000 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.projectforge.business.poll; - -import org.projectforge.framework.access.*; -import org.projectforge.framework.persistence.api.*; -import org.projectforge.framework.persistence.user.entities.*; -import org.springframework.context.annotation.*; -import org.springframework.stereotype.*; - -@Repository -public class PollDao extends BaseDao { - - public PollDao() { - super(PollDO.class); - } - - @Override - public boolean hasAccess(PFUserDO user, PollDO obj, PollDO oldObj, OperationType operationType, boolean throwException) { - return true; - } - - @Override - public PollDO newInstance() { - return new PollDO(); - } - -} diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt new file mode 100644 index 0000000000..89d2c5d112 --- /dev/null +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -0,0 +1,47 @@ +package org.projectforge.business.poll + +import org.projectforge.framework.access.OperationType +import org.projectforge.framework.persistence.api.BaseDao +import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext +import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext.user +import org.projectforge.framework.persistence.user.entities.PFUserDO +import org.springframework.stereotype.Repository + +@Repository +open class PollDao : BaseDao(PollDO::class.java){ + + override fun newInstance(): PollDO { + return PollDO() + } + + override fun hasAccess( + user: PFUserDO?, + obj: PollDO?, + oldObj: PollDO?, + operationType: OperationType?, + throwException: Boolean + ): Boolean { + /*val loggedInUserId = ThreadLocalUserContext.user?.id + if (obj == null) { + return true + } + if (obj.fullAccessUserIds!!.contains(loggedInUserId.toString())) { + return true + }*/ + if (operationType == OperationType.SELECT){ + return true + }; + return false + } + + + override fun onSaveOrModify(obj: PollDO) { + val loggedInUser = user + val userString = obj.fullAccessUserIds + if (loggedInUser != null && !obj.fullAccessUserIds!!.contains(loggedInUser.id.toString())) { + obj.fullAccessUserIds = userString.plus(", ${loggedInUser.id}"); + } + // Prüfen, ob loggedInUser in accessUsers, wenn nicht, hinuif+gen + super.onSaveOrModify(obj) + } +} \ No newline at end of file diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql similarity index 89% rename from projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql rename to projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql index 3614f0c970..c0ea757365 100644 --- a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql @@ -10,7 +10,8 @@ CREATE TABLE T_POLL description CHARACTER VARYING(1000), location CHARACTER VARYING(1000), owner_pk INTEGER NOT NULL, - deadline DATE NOT NULL + deadline DATE NOT NULL, + accessUser CHARACTER VARYING(1000) ); ALTER TABLE T_POLL diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.4__7.5.1.0-POLL.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.4__7.5.1.0-POLL.sql new file mode 100644 index 0000000000..372246fd24 --- /dev/null +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.4__7.5.1.0-POLL.sql @@ -0,0 +1,7 @@ +-- Table with synchronize infos (Sipgate) + +ALTER TABLE T_POLL + DROP COLUMN accessUser; + +ALTER TABLE T_POLL + ADD full_access_user_ids CHARACTER VARYING(255); diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.5__7.5.1.0-POLL.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.5__7.5.1.0-POLL.sql new file mode 100644 index 0000000000..300895d089 --- /dev/null +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.5__7.5.1.0-POLL.sql @@ -0,0 +1,7 @@ +-- Table with synchronize infos (Sipgate) +ALTER TABLE T_POLL + ADD minimal_access_user_ids CHARACTER VARYING(255); +ALTER TABLE T_POLL + ADD readonly_access_group_ids CHARACTER VARYING(255); +ALTER TABLE T_POLL + ADD readonly_access_user_ids CHARACTER VARYING(255); \ No newline at end of file diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.6__7.5.1.0-POLL.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.6__7.5.1.0-POLL.sql new file mode 100644 index 0000000000..b8094ec61e --- /dev/null +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.6__7.5.1.0-POLL.sql @@ -0,0 +1,4 @@ +ALTER TABLE T_POLL + ADD full_access_group_ids CHARACTER VARYING(255); +ALTER TABLE T_POLL + ADD minimal_access_group_ids CHARACTER VARYING(255); \ No newline at end of file diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.7__7.5.1.0-POLL.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.7__7.5.1.0-POLL.sql new file mode 100644 index 0000000000..70d6aac5d6 --- /dev/null +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.7__7.5.1.0-POLL.sql @@ -0,0 +1,2 @@ +ALTER TABLE T_POLL + ADD date Date; \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index 2f909a628d..427d29183a 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -1,12 +1,11 @@ package org.projectforge.rest.poll -import net.sf.mpxj.LocaleData import org.projectforge.business.poll.PollDO import org.projectforge.framework.persistence.user.entities.PFUserDO import org.projectforge.rest.dto.BaseDTO -import org.projectforge.ui.UIDataType +import org.projectforge.rest.dto.Group +import org.projectforge.rest.dto.User import java.time.LocalDate -import java.util.* class Poll( var title: String? = null, @@ -15,14 +14,19 @@ class Poll( var location: String? = null, var date: LocalDate? = null, var deadline: LocalDate? = null, - var inputFields: List? = null, - var canSeeResultUsers: String? = null, - var canEditPollUsers: String? = null, - var canVoteInPoll: String? = null -) : BaseDTO() { - class InputField( - var type: UIDataType? = null, - var name: String? = null, - var value: Any? = null, - ) + var fullAccessGroups: List? = null, + var fullAccessUsers: List? = null + ) : BaseDTO() { + override fun copyFrom(src: PollDO) { + super.copyFrom(src) + fullAccessGroups = Group.toGroupList(src.fullAccessGroupIds) + fullAccessUsers = User.toUserList(src.fullAccessUserIds) + } + + // The user and group ids are stored as csv list of integers in the data base. + override fun copyTo(dest: PollDO) { + super.copyTo(dest) + dest.fullAccessGroupIds = Group.toIntList(fullAccessGroups) + dest.fullAccessUserIds = User.toIntList(fullAccessUsers) + } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 0e98f0d98d..a4972b5b79 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -2,16 +2,18 @@ package org.projectforge.rest.poll import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao -import org.projectforge.business.vacation.model.VacationDO +import org.projectforge.business.user.service.UserPrefService +import org.projectforge.business.user.service.UserService import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType import org.projectforge.rest.VacationExportPageRest import org.projectforge.rest.config.Rest import org.projectforge.rest.core.* -import org.projectforge.rest.dto.PostData -import org.projectforge.rest.dto.Vacation +import org.projectforge.rest.dto.Group +import org.projectforge.rest.dto.User import org.projectforge.ui.* +import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestMapping @@ -22,6 +24,9 @@ import javax.servlet.http.HttpServletRequest @RequestMapping("${Rest.URL}/poll") class PollPageRest : AbstractDTOPagesRest(PollDao::class.java, "poll.title") { + @Autowired + private lateinit var userService: UserService; + override fun transformForDB(dto: Poll): PollDO { val pollDO = PollDO() dto.copyTo(pollDO) @@ -31,6 +36,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun transformFromDB(obj: PollDO, editMode: Boolean): Poll { val poll = Poll() poll.copyFrom(obj) + User.restoreDisplayNames(poll.fullAccessUsers, userService) return poll } @@ -45,6 +51,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. .add(lc, "title", "description", "location") .add(lc, "owner") .add(lc, "deadline") + .add(lc, "date") layout.add( MenuItem( "export", @@ -64,16 +71,46 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. UIRow() .add( UIFieldset(UILength(md = 6, lg = 4)) - .add(lc, "title", "description", "location", "owner", "deadline") + .add(lc, "title", "description", "location", "owner", "deadline", "date") + .add(UISelect.createUserSelect(lc, "fullAccessUsers", true, "poll.fullAccessUsers")) + .add(UISelect.createGroupSelect(lc, "fullAccessGroups", true, "poll.fullAccessGroups")) + /*.add( + UIFieldset(UILength(md = 12, lg = 12), title = "access.title.heading") + .add( + UIRow() + .add( + UIFieldset(6, title = "access.users") + .add(UISelect.createUserSelect(lc, "fullAccessUsers", true, "plugins.banking.account.fullAccess")) + .add( + UISelect.createUserSelect( + lc, + "readonlyAccessUsers", + true, + "plugins.banking.account.readonlyAccess" + ) + ) + ) + .add( + UIFieldset(6, title = "access.groups") + .add(UISelect.createGroupSelect(lc, "fullAccessGroups", true, "plugins.banking.account.fullAccess")) + .add( + UISelect.createGroupSelect( + lc, + "readonlyAccessGroups", + true, + "plugins.banking.account.readonlyAccess" + ) + ) + )*/ )) .add(UIButton.createAddButton(responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST))) - layout.watchFields.addAll( arrayOf( "title", "description", "location", - "deadline" + "deadline", + "date" ) ) updateStats(dto) @@ -89,6 +126,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val description = dto.description val location = dto.location val deadline = dto.deadline + val date = dto.date updateStats(dto) val userAccess = UILayout.UserAccess() @@ -108,16 +146,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val description = dto.description val location = dto.location val deadline = dto.deadline + val date = dto.date val pollDO = PollDO() dto.copyTo(pollDO) } - // PostMapping add - @PostMapping("/add") - fun abc(){ - } - /*dto.inputFields?.forEachIndexed { field, index -> if (field.type == msc) { @@ -128,5 +162,5 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } layout.add(UIRow().add(UIFieldset(UILength(md = 6, lg = 4)) .add(lc, "name"))) - */ + */ } \ No newline at end of file From aeb60d7349be992fd928de7056f8e511935dc453 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Tue, 18 Apr 2023 09:35:29 +0200 Subject: [PATCH 028/160] add anleitungs Page --- .../rest/poll/PollInfoPageRest.kt | 54 +++++++++++++++++++ .../projectforge/rest/poll/PollPageRest.kt | 17 ++++++ 2 files changed, 71 insertions(+) create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt new file mode 100644 index 0000000000..5571090887 --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt @@ -0,0 +1,54 @@ +package org.projectforge.rest.poll + +import org.projectforge.business.user.UserAuthenticationsService +import org.projectforge.business.user.UserTokenType +import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext +import org.projectforge.rest.config.Rest +import org.projectforge.rest.dto.FormLayoutData +import org.projectforge.ui.* +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import javax.servlet.http.HttpServletRequest + + +@RestController +@RequestMapping("${Rest.URL}/pollInfo") +class PollInfoPageRest { + class Data( + var user: String? = null, + var password: String? = null, + var server: String? = null, + var serverPath: String? = null, + var ios: String? = null, + var thunderbird: String? = null + ) + + @Autowired + private lateinit var authenticationsService: UserAuthenticationsService + + @GetMapping("dynamic") + fun getForm(request: HttpServletRequest): FormLayoutData { + val username = ThreadLocalUserContext.user?.username ?: "?????" + val layout = UILayout("poll.infopage") + .add(UILabel(""" Eine Poll """)) + layout.add(UICol() + .add(UIReadOnlyField("user", label = "user"))) + + + LayoutUtils.process(layout) + + val data = Data( + user = username, + password = authenticationsService.getToken(ThreadLocalUserContext.userId!!, UserTokenType.DAV_TOKEN), + server = request.serverName, + serverPath = "/users/${username}/addressBooks/default", + ios = "CardDAV account", + thunderbird = "CardDAV account" + ) + + return FormLayoutData(data, layout, null) + } + +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index ba5cf6e739..57c7f45c26 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -7,6 +7,7 @@ import org.projectforge.business.poll.PollDao import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType +import org.projectforge.rest.CardDAVInfoPageRest import org.projectforge.rest.VacationExportPageRest import org.projectforge.rest.config.Rest import org.projectforge.rest.core.AbstractDTOPagesRest @@ -72,6 +73,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val obj = PollDO() dto.copyTo(obj) val layout = super.createEditLayout(dto, userAccess) + layout.add(MenuItem("zt", "moin", url = PagesResolver.getDynamicPageUrl(PollInfoPageRest::class.java), type = MenuItemTargetType + .MODAL)) layout.add( UIRow().add( UIFieldset(UILength(md = 6, lg = 4)).add(lc, "title", "description", "location", "owner", "deadline") @@ -97,6 +100,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. "title", "description", "location", "deadline" ) ) + return LayoutUtils.processEditPage(layout, dto, this) } @@ -117,6 +121,19 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) } + @GetMapping("anleitung") + fun rendernleitung():UILayout{ + val layout = UILayout("poll.anleitung") + layout.add( + UIRow().add( + UIFieldset(UILength(md = 6, lg = 4)).add( + UILabel("poll.anleitung") + ) + ) + ) + + return layout + } @PostMapping("/addAntwort/{fieldId}") fun addAntwortFeld( From 615e889cc1f6a93339586c8a42007f690745f6c6 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Tue, 18 Apr 2023 10:12:18 +0200 Subject: [PATCH 029/160] add email content by finish --- .../projectforge/rest/poll/PollPageRest.kt | 63 +++++++++++-------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 90f8c8e34e..79459fd1e3 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -170,19 +170,52 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. * Method to end polls after deadline */ fun cronEndPolls() { - val polls = pollDao.internalLoadAll() + var mail = ""; + var header = ""; + + + + val polls = pollDao.internalLoadAll() + val list = ArrayList() // set State.FINISHED for all old polls polls.forEach { - if (it.deadline?.isBefore(LocalDate.now()) == true) { + if (it.deadline?.isBefore(LocalDate.now().minusDays(1)) == true) { it.state = PollDO.State.FINISHED + // check if state is open or closed + + val ihkExporter = ExcelExport() + + val poll = Poll() + poll.copyFrom(it) + + val exel = ihkExporter + .getExcel(poll) + + val attachment = object : MailAttachment { + override fun getFilename(): String { + return it.title+ "_" + LocalDateTime.now().year +"_Result"+ ".xlsx" + } + + override fun getContent(): ByteArray? { + return exel + } + } + list.add(attachment) + + + header = "Umfrage ist abgelaufen" + mail =""" + Die Umfrage ist zu ende. Hier die ergebnisse. + """.trimMargin() + pollDao.internalSaveOrUpdate(it) } + } try { - var mail = ""; - var header = ""; + // erstell mir eine funktion, die alles deadlines mir gibt die in der zukunft liegen val pollsInFuture = polls.filter { it.deadline?.isAfter(LocalDate.now()) ?: false} pollsInFuture.forEach{ @@ -205,27 +238,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } } - // check if state is open or closed - val list = ArrayList() - - val ihkExporter = ExcelExport() - // val exel = ihkExporter - // .getExcel() - - val attachment = object : MailAttachment { - override fun getFilename(): String { - return "test"+ "_" + LocalDateTime.now().year + ".xlsx" - } - - override fun getContent(): ByteArray? { - // return exel - return null - } - } - list.add(attachment) - if(mail.isNotEmpty()){ pollMailService.sendMail(to="test", subject = header, content = mail, mailAttachments = list) } @@ -239,8 +253,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. /** * Cron job for daily stuff */ - //@Scheduled(cron = "0 0 1 * * *") // 1am everyday - @Scheduled(cron = "0 * * * * *") // 1am everyday + @Scheduled(cron = "0 0 1 * * *") // 1am everyday fun dailyCronJobs() { cronDeletePolls() cronEndPolls() From 8c203f33e9d96deaf6502c64765016829da535fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 18 Apr 2023 10:16:38 +0200 Subject: [PATCH 030/160] Removed stupid id --- .../src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 79459fd1e3..3645e8c1ce 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -71,7 +71,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun createListLayout( request: HttpServletRequest, layout: UILayout, magicFilter: MagicFilter, userAccess: UILayout.UserAccess ) { - var id = 10094 agGridSupport.prepareUIGrid4ListPage( request, layout, From 96dfd0a40dd35819a910169e917f1e3d7775fe88 Mon Sep 17 00:00:00 2001 From: Qangeldratsch <48660866+Qangeldratsch@users.noreply.github.com> Date: Tue, 18 Apr 2023 10:32:10 +0200 Subject: [PATCH 031/160] Hotfix: Removed id stuff --- .../main/kotlin/org/projectforge/rest/poll/PollPageRest.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index c8a4b22105..3e050f6a04 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -91,8 +91,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. type = MenuItemTargetType.REDIRECT, ) ) - layout.add(UIButton.createAddButton(responseAction = ResponseAction("${Rest.URL}/poll/edit?id=${id}", targetType = TargetType.GET))) - } @@ -400,4 +398,4 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. layout.add(UIRow().add(UIFieldset(UILength(md = 6, lg = 4)) .add(lc, "name"))) */ -} \ No newline at end of file +} From 8afc82402e4dcfd5175c5139ba50ceeda7d0400d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Wed, 19 Apr 2023 11:12:58 +0200 Subject: [PATCH 032/160] Created PremadeQuestions and added functionality for adding them --- .../V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql | 2 +- .../org/projectforge/rest/poll/CronJobs.kt | 133 ++++++++++ .../kotlin/org/projectforge/rest/poll/Poll.kt | 4 +- .../projectforge/rest/poll/PollPageRest.kt | 249 ++++++------------ .../rest/poll/types/PremadeQuestions.kt | 36 +++ .../rest/poll/types/{Frage.kt => Question.kt} | 19 +- .../poll/types/{FrageDO.kt => QuestionDO.kt} | 2 +- 7 files changed, 260 insertions(+), 185 deletions(-) create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt rename projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/{Frage.kt => Question.kt} (54%) rename projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/{FrageDO.kt => QuestionDO.kt} (69%) diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql index 2fecb4c36e..3a4feb1ff7 100644 --- a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql @@ -13,7 +13,7 @@ CREATE TABLE T_POLL deadline DATE NOT NULL, date DATE, state CHARACTER VARYING(1000), - inputFields CHARACTER Varying(1000) + inputFields CHARACTER Varying(100000) /* canSeeResultUsers CHARACTER VARYING(1000), canEditPollUsers CHARACTER VARYING(1000), diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt new file mode 100644 index 0000000000..f206c513d4 --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt @@ -0,0 +1,133 @@ +package org.projectforge.rest.poll + +import org.projectforge.business.poll.PollDO +import org.projectforge.business.poll.PollDao +import org.projectforge.mail.MailAttachment +import org.projectforge.rest.poll.Exel.ExcelExport +import org.springframework.beans.factory.annotation.Autowired +import java.util.* +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.web.bind.annotation.RestController +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.temporal.ChronoUnit +import kotlin.collections.ArrayList +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +@RestController +class CronJobs { + + private val log: Logger = LoggerFactory.getLogger(CronJobs::class.java) + @Autowired + private lateinit var pollDao: PollDao + @Autowired + private lateinit var pollMailService: PollMailService + + /** + * Method to end polls after deadline + */ + fun cronEndPolls() { + var mail = ""; + var header = ""; + + val polls = pollDao.internalLoadAll() + val list = ArrayList() + // set State.FINISHED for all old polls + polls.forEach { + if (it.deadline?.isBefore(LocalDate.now().minusDays(1)) == true) { + it.state = PollDO.State.FINISHED + // check if state is open or closed + + val ihkExporter = ExcelExport() + + val poll = Poll() + poll.copyFrom(it) + + val exel = ihkExporter + .getExcel(poll) + + val attachment = object : MailAttachment { + override fun getFilename(): String { + return it.title+ "_" + LocalDateTime.now().year +"_Result"+ ".xlsx" + } + + override fun getContent(): ByteArray? { + return exel + } + } + list.add(attachment) + + + header = "Umfrage ist abgelaufen" + mail =""" + Die Umfrage ist zu ende. Hier die ergebnisse. + """.trimMargin() + + pollDao.internalSaveOrUpdate(it) + } + + } + + try { + + // erstell mir eine funktion, die alles deadlines mir gibt die in der zukunft liegen + val pollsInFuture = polls.filter { it.deadline?.isAfter(LocalDate.now()) ?: false} + pollsInFuture.forEach{ + val daysDifference = ChronoUnit.DAYS.between(LocalDate.now(), it.deadline) + if(daysDifference == 1L || daysDifference == 7L){ + header = "Umfrage Endet in $daysDifference Tage" + mail =""" + Sehr geehrter Teilnehmer,wir laden Sie herzlich dazu ein, an unserer Umfrage zum Thema ${it.title} teilzunehmen. + Ihre Meinung ist uns sehr wichtig und wir würden uns freuen, wenn Sie uns dabei helfen könnten, + unsere Forschungsergebnisse zu verbessern. Für diese Umfrage ist ${it ///owmer + }zuständig. + Bei Fragen oder Anmerkungen können Sie sich gerne an ihn wenden. + Bitte beachten Sie, dass das Enddatum für die Teilnahme an dieser Umfrage der ${it.deadline.toString()} ist. + Wir würden uns freuen, wenn Sie sich die Zeit nehmen könnten, um diese Umfrage auszufüllen. + Vielen Dank im Voraus für Ihre Unterstützung. + + Mit freundlichen Grüßen,${it ///owmer + } + """.trimMargin() + } + } + + + + if(mail.isNotEmpty()){ + pollMailService.sendMail(to="test", subject = header, content = mail, mailAttachments = list) + } + } + catch (e:Exception) { + log.error(e.toString()) + } + } + + + /** + * Cron job for daily stuff + */ + @Scheduled(cron = "0 0 1 * * *") // 1am everyday + fun dailyCronJobs() { + cronDeletePolls() + cronEndPolls() + } + + + /** + * Method to delete old polls + */ + fun cronDeletePolls() { + // check if poll end in Future + val polls = pollDao.internalLoadAll() + val pollsMoreThanOneYearPast = polls.filter { it.created?.before(Date.from(LocalDate.now().minusYears(1).atStartOfDay( + ZoneId.systemDefault() + ).toInstant())) ?: false } + pollsMoreThanOneYearPast.forEach { + pollDao.delete(it) + } + } + +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index d1ef25fe1e..ef9978bbe9 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -3,7 +3,7 @@ package org.projectforge.rest.poll import org.projectforge.business.poll.PollDO import org.projectforge.framework.persistence.user.entities.PFUserDO import org.projectforge.rest.dto.BaseDTO -import org.projectforge.rest.poll.types.Frage +import org.projectforge.rest.poll.types.Question import java.time.LocalDate class Poll( @@ -15,7 +15,7 @@ class Poll( var deadline: LocalDate? = null, var state: PollDO.State? = PollDO.State.RUNNING, var questionType: String? = null, - var inputFields: MutableList? = null, + var inputFields: MutableList? = mutableListOf(), var canSeeResultUsers: String? = null, var canEditPollUsers: String? = null, var canVoteInPoll: String? = null diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 3e050f6a04..69b3a48318 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -4,19 +4,15 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao import org.projectforge.framework.persistence.api.MagicFilter -import org.projectforge.mail.MailAttachment -import org.projectforge.menu.MenuItem -import org.projectforge.menu.MenuItemTargetType -import org.projectforge.rest.VacationExportPageRest import org.projectforge.rest.config.Rest import org.projectforge.rest.core.* import org.projectforge.rest.config.RestUtils import org.projectforge.rest.core.AbstractDTOPagesRest -import org.projectforge.rest.core.PagesResolver import org.projectforge.rest.dto.PostData import org.projectforge.rest.poll.Exel.ExcelExport import org.projectforge.rest.poll.types.BaseType -import org.projectforge.rest.poll.types.Frage +import org.projectforge.rest.poll.types.PREMADE_QUESTIONS +import org.projectforge.rest.poll.types.Question import org.projectforge.ui.* import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -25,27 +21,19 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import java.util.* -import org.springframework.scheduling.annotation.Scheduled import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import java.time.LocalDate -import java.time.LocalDateTime -import java.time.ZoneId -import java.time.temporal.ChronoUnit import javax.servlet.http.HttpServletRequest -import kotlin.collections.ArrayList @RestController @RequestMapping("${Rest.URL}/poll") class PollPageRest : AbstractDTOPagesRest(PollDao::class.java, "poll.title") { - private val log: Logger = LoggerFactory.getLogger(PollPageRest::class.java) @Autowired private lateinit var pollDao: PollDao - @Autowired private lateinit var pollMailService: PollMailService @@ -60,12 +48,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. //override fun transformForDB editMode not used - override fun transformFromDB(obj: PollDO, editMode: Boolean): Poll { + override fun transformFromDB(pollDO: PollDO, editMode: Boolean): Poll { val poll = Poll() - poll.copyFrom(obj) - if(obj.inputFields!= null){ - var a = ObjectMapper().readValue(obj.inputFields, MutableList::class.java) - poll.inputFields = a.map { Frage().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() + poll.copyFrom(pollDO) + if (pollDO.inputFields != null) { + var a = ObjectMapper().readValue(pollDO.inputFields, MutableList::class.java) + poll.inputFields = a.map { Question().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() } return poll } @@ -81,23 +69,13 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. userAccess = userAccess, ) .add(lc, "title", "description", "location", "owner", "deadline", "state") - - - layout.add( - MenuItem( - "export", - i18nKey = "poll.export.title", - url = PagesResolver.getDynamicPageUrl(VacationExportPageRest::class.java), - type = MenuItemTargetType.REDIRECT, - ) - ) } override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { val lc = LayoutContext(PollDO::class.java) - val obj = PollDO() - dto.copyTo(obj) + val poll = PollDO() + dto.copyTo(poll) val layout = super.createEditLayout(dto, userAccess) layout.add( UIRow().add( @@ -107,15 +85,29 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. layout.add( UIRow().add( UIFieldset(UILength(md = 6, lg = 4)).add( - UIButton.createAddButton( - responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST) + UIButton.createDefaultButton( + id = "add-question-button", + responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST), + title = "Eigene Frage hinzufügen" ) ).add( UISelect("questionType", values = BaseType.values().map { UISelectValue(it, it.name) }) + ) + ) + ) + layout.add( + UIRow().add( + UIFieldset(UILength(md = 6, lg = 4)).add( + UIButton.createDefaultButton( + id = "micromata-vorlage-button", + responseAction = ResponseAction("${Rest.URL}/poll/addPremadeQuestions", targetType = TargetType.POST), + title = "Micromata Vorlage nutzen" + ) ) ) ) + addQuestionFieldset(layout, dto) layout.watchFields.addAll( @@ -123,9 +115,13 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. "title", "description", "location", "deadline" ) ) + return LayoutUtils.processEditPage(layout, dto, this) } + + //TODO refactor this whole file into multiple smaller files + override fun onWatchFieldsUpdate( request: HttpServletRequest, dto: Poll, watchFieldsTriggered: Array? ): ResponseEntity { @@ -143,12 +139,14 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) } - override fun onAfterSaveOrUpdate(request: HttpServletRequest, obj: PollDO, postData: PostData) { - super.onAfterSaveOrUpdate(request, obj, postData) + + override fun onAfterSaveOrUpdate(request: HttpServletRequest, poll: PollDO, postData: PostData) { + super.onAfterSaveOrUpdate(request, poll, postData) val dto = postData.data pollMailService.sendMail(subject = "", content = "", to = "test.mail") } + @PostMapping("/addAntwort/{fieldId}") fun addAntwortFeld( @RequestBody postData: PostData, @@ -158,148 +156,54 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val userAccess = UILayout.UserAccess(insert = true, update = true) val found = dto.inputFields?.find { it.uid == fieldUid } - found?.antworten?.add("") + found?.answers?.add("") return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) } - /** - * Method to end polls after deadline - */ - fun cronEndPolls() { - - var mail = ""; - var header = ""; - - - - val polls = pollDao.internalLoadAll() - val list = ArrayList() - // set State.FINISHED for all old polls - polls.forEach { - if (it.deadline?.isBefore(LocalDate.now().minusDays(1)) == true) { - it.state = PollDO.State.FINISHED - // check if state is open or closed - - val ihkExporter = ExcelExport() - - val poll = Poll() - poll.copyFrom(it) - - val exel = ihkExporter - .getExcel(poll) - - val attachment = object : MailAttachment { - override fun getFilename(): String { - return it.title+ "_" + LocalDateTime.now().year +"_Result"+ ".xlsx" - } - - override fun getContent(): ByteArray? { - return exel - } - } - list.add(attachment) - - - header = "Umfrage ist abgelaufen" - mail =""" - Die Umfrage ist zu ende. Hier die ergebnisse. - """.trimMargin() - - pollDao.internalSaveOrUpdate(it) - } - - } - - try { - - // erstell mir eine funktion, die alles deadlines mir gibt die in der zukunft liegen - val pollsInFuture = polls.filter { it.deadline?.isAfter(LocalDate.now()) ?: false} - pollsInFuture.forEach{ - val daysDifference = ChronoUnit.DAYS.between(LocalDate.now(), it.deadline) - if(daysDifference == 1L || daysDifference == 7L){ - header = "Umfrage Endet in $daysDifference Tage" - mail =""" - Sehr geehrter Teilnehmer,wir laden Sie herzlich dazu ein, an unserer Umfrage zum Thema ${it.title} teilzunehmen. - Ihre Meinung ist uns sehr wichtig und wir würden uns freuen, wenn Sie uns dabei helfen könnten, - unsere Forschungsergebnisse zu verbessern. Für diese Umfrage ist ${it ///owmer - }zuständig. - Bei Fragen oder Anmerkungen können Sie sich gerne an ihn wenden. - Bitte beachten Sie, dass das Enddatum für die Teilnahme an dieser Umfrage der ${it.deadline.toString()} ist. - Wir würden uns freuen, wenn Sie sich die Zeit nehmen könnten, um diese Umfrage auszufüllen. - Vielen Dank im Voraus für Ihre Unterstützung. - - Mit freundlichen Grüßen,${it ///owmer - } - """.trimMargin() - } - } + // PostMapping add + @PostMapping("/add") + fun addQuestionField( + @RequestBody postData: PostData + ): ResponseEntity { + val userAccess = UILayout.UserAccess(insert = true, update = true) + val dto = postData.data + val poll = PollDO() - if(mail.isNotEmpty()){ - pollMailService.sendMail(to="test", subject = header, content = mail, mailAttachments = list) - } + var type = BaseType.valueOf(dto.questionType ?: "TextQuestion") + var question = Question(uid = UUID.randomUUID().toString(), type = type) + if(type == BaseType.YesNoQuestion) { + question.answers = mutableListOf("ja", "nein") } - catch (e:Exception) { - log.error(e.toString()) + if(type == BaseType.DateQuestion) { + question.answers = mutableListOf("Ja", "Vielleicht", "Nein") } - } - - - /** - * Cron job for daily stuff - */ - @Scheduled(cron = "0 0 1 * * *") // 1am everyday - fun dailyCronJobs() { - cronDeletePolls() - cronEndPolls() - } + dto.inputFields!!.add(question) - /** - * Method to delete old polls - */ - fun cronDeletePolls() { - // check if poll end in Future - val polls = pollDao.internalLoadAll() - val pollsMoreThanOneYearPast = polls.filter { it.created?.before(Date.from(LocalDate.now().minusYears(1).atStartOfDay( - ZoneId.systemDefault() - ).toInstant())) ?: false } - pollsMoreThanOneYearPast.forEach { - pollDao.delete(it) - } + dto.copyTo(poll) + return ResponseEntity.ok( + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ) } - - // PostMapping add - @PostMapping("/add") - fun addQuestionField( + @PostMapping("/addPremadeQuestions") + private fun addPremadeQuestionsField( @RequestBody postData: PostData, ): ResponseEntity { val userAccess = UILayout.UserAccess(insert = true, update = true) val dto = postData.data - var type = BaseType.valueOf(dto.questionType ?: "FreiTextFrage") - - val poll = PollDO() - if (dto.inputFields == null) { - dto.inputFields = mutableListOf() - } - var frage = Frage(uid = UUID.randomUUID().toString(), type = type) - if(type== BaseType.JaNeinFrage) { - frage.antworten = mutableListOf("ja", "nein") - } - if(type== BaseType.DatumsAbfrage) { - frage.antworten = mutableListOf("Ja", "Vielleicht", "Nein") + PREMADE_QUESTIONS.entries.forEach { entry -> + dto.inputFields?.add(entry.value) } - dto.inputFields!!.add(frage) - dto.copyTo(poll) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) @@ -308,35 +212,34 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. private fun addQuestionFieldset(layout: UILayout, dto: Poll) { - dto.inputFields?.forEachIndexed { index, field -> - val feld = UIRow() - if (field.type == BaseType.JaNeinFrage) { + val row = UIRow() + if (field.type == BaseType.YesNoQuestion) { val groupLayout = UIGroup() - field.antworten?.forEach { antwort -> + field.answers?.forEach { answer -> groupLayout.add( UIRadioButton( - "JaNeinRadio", antwort, label = antwort + "YesNoQuestion[${index}].question", answer, label = answer ) ) } - feld.add( + row.add( UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add(UIInput("inputFields[${index}].question")).add (groupLayout) ) } - if (field.type == BaseType.FreiTextFrage) { - feld.add( + if (field.type == BaseType.TextQuestion) { + row.add( UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add(UIInput("inputFields[${index}].question")) ) } - if (field.type == BaseType.MultipleChoices || field.type == BaseType.DropDownFrage) { + if (field.type == BaseType.MultipleChoices || field.type == BaseType.DropDownQuestion) { val f = UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()) .add(UIInput("inputFields[${index}].question", label = "Die Frage")) - field.antworten?.forEachIndexed { i, _ -> - f.add(UIInput("inputFields[${index}].antworten[${i}]", label = "AntwortMöglichkeit ${i + 1}")) + field.answers?.forEachIndexed { i, _ -> + f.add(UIInput("inputFields[${index}].answers[${i}]", label = "Antwortmöglichkeit ${i + 1}")) } f.add( UIButton.createAddButton( @@ -348,15 +251,16 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. if (field.type == BaseType.MultipleChoices) { f.add( UIInput( - "inputFields[${index}].numberOfSelect", dataType = UIDataType.INT, label = "Wie viele Sollen " + - "angeklickt werden können " + "inputFields[${index}].numberOfSelect", dataType = UIDataType.INT, label = "Wie viele sollen " + + "angeklickt werden können?" ) ) } - feld.add(f) + row.add(f) } - if (field.type == BaseType.DatumsAbfrage) { - feld.add( + + if (field.type == BaseType.DateQuestion) { + row.add( UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add( UIInput( "inputFields[${index}].question", @@ -366,7 +270,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) } - layout.add(feld) + + layout.add(row) } } @@ -379,13 +284,13 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val filename = ("test.xlsx") if (bytes == null || bytes.size == 0) { - log.error("Oups, xlsx has zero size. Filename: $filename") + log.error("Oops, xlsx has zero size. Filename: $filename") return null; } return RestUtils.downloadFile(filename, bytes) } - // create a update layout funktion, welche das lyout nummr updatet und rurück gibt es soll für jeden Frage Basistyp eine eigene funktion haben + // create a update layout funktion, welche das layout nummr updatet und zurück gibt es soll für jeden Frage Basistyp eine eigene funktion haben /*dto.inputFields?.forEachIndexed { field, index -> diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt new file mode 100644 index 0000000000..7e5f69784c --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt @@ -0,0 +1,36 @@ +package org.projectforge.rest.poll.types + + +val PREMADE_QUESTIONS = mapOf( + "HAS_FOOD" to Question( + question = "Was willst du essen?", + type = BaseType.MultipleChoices, + answers = mutableListOf("Fleisch", "Vegetarisch", "Vegan"), + numberOfSelect = 1 + ), + "IS_REMOTE" to Question( + question = "Nimmst du remote teil?", + type = BaseType.YesNoQuestion, + answers = mutableListOf("Ja", "Nein") + ), + "CAN_HAVE_COMPANIONS" to Question( + question = "Nimmst du eine Begleitung mit? (Name der Begleitung)", + type = BaseType.TextQuestion, + answers = mutableListOf("") + ), + "CAN_HAVE_CHILDREN" to Question( + question = "Nimmst du ein Kind mit? (Name der Begleitung)", + type = BaseType.TextQuestion, + answers = mutableListOf("") + ), + "CAN_STAY_OVERNIGHT" to Question( + question = "Willst du dort übernachten?", + type = BaseType.YesNoQuestion, + answers = mutableListOf("Ja", "Nein") + ), + "HAS_BREAKFAST" to Question( + question = "Willst du am nächsten Tag frühstücken?", + type = BaseType.YesNoQuestion, + answers = mutableListOf("Ja", "Nein") + ), +) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Frage.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt similarity index 54% rename from projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Frage.kt rename to projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt index 0ce19e8d4b..388f6bd272 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Frage.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt @@ -3,17 +3,17 @@ package org.projectforge.rest.poll.types import com.fasterxml.jackson.databind.ObjectMapper -class Frage( +class Question( val uid: String? = null, val question: String? = "", - val type: BaseType? = BaseType.FreiTextFrage, - var antworten: MutableList? = mutableListOf(""), + val type: BaseType? = BaseType.TextQuestion, + var answers: MutableList? = mutableListOf(""), var parent: String? = null, var isRequired: Boolean? = false, var numberOfSelect: Int? = 1, ){ - fun toObject(string:String): Frage { - return ObjectMapper().readValue(string, Frage::class.java) + fun toObject(string:String): Question { + return ObjectMapper().readValue(string, Question::class.java) } fun toJson(): String { return ObjectMapper().writeValueAsString(this) @@ -21,9 +21,10 @@ class Frage( } enum class BaseType { - JaNeinFrage, - DatumsAbfrage, + YesNoQuestion, + DateQuestion, MultipleChoices, - FreiTextFrage, - DropDownFrage + TextQuestion, + DropDownQuestion, + PremadeQuestion } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/FrageDO.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/QuestionDO.kt similarity index 69% rename from projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/FrageDO.kt rename to projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/QuestionDO.kt index 47eb81490c..c9d74e7b64 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/FrageDO.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/QuestionDO.kt @@ -1,4 +1,4 @@ package org.projectforge.rest.poll.types -class FrageDO { +class QuestionDO { } \ No newline at end of file From 9e448d5288c7b8f71fd724bb7f429c1b8c400366 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 19 Apr 2023 13:02:06 +0200 Subject: [PATCH 033/160] create a PollInfoPage --- .../rest/poll/PollInfoPageRest.kt | 94 +++++++++++++------ .../projectforge/rest/poll/PollPageRest.kt | 11 +-- 2 files changed, 67 insertions(+), 38 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt index 5571090887..b6f65b3b2e 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt @@ -1,12 +1,10 @@ package org.projectforge.rest.poll -import org.projectforge.business.user.UserAuthenticationsService -import org.projectforge.business.user.UserTokenType -import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.rest.config.Rest +import org.projectforge.rest.core.AbstractDynamicPageRest +import org.projectforge.rest.core.PagesResolver import org.projectforge.rest.dto.FormLayoutData import org.projectforge.ui.* -import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @@ -15,40 +13,78 @@ import javax.servlet.http.HttpServletRequest @RestController @RequestMapping("${Rest.URL}/pollInfo") -class PollInfoPageRest { - class Data( - var user: String? = null, - var password: String? = null, - var server: String? = null, - var serverPath: String? = null, - var ios: String? = null, - var thunderbird: String? = null - ) - - @Autowired - private lateinit var authenticationsService: UserAuthenticationsService +class PollInfoPageRest: AbstractDynamicPageRest() { @GetMapping("dynamic") fun getForm(request: HttpServletRequest): FormLayoutData { - val username = ThreadLocalUserContext.user?.username ?: "?????" + val layout = UILayout("poll.infopage") - .add(UILabel(""" Eine Poll """)) - layout.add(UICol() - .add(UIReadOnlyField("user", label = "user"))) + val field = UIFieldset() + .add(UILabel(""" Anleitung um eine Umfrage zu erstellen + | Als erstes werden die Parameter einer Umfrage angelegt. + """.trimMargin())) + field.add(UICol() + .add(UIReadOnlyField("title", label = "title", value = "Test"))) + field.add(UICol() + .add(UIReadOnlyField("description", label = "description", value = "description"))) + field.add(UICol() + .add(UIReadOnlyField("location", label = "location", value = "location"))) + field.add(UICol() + .add(UIReadOnlyField("owner", label = "owner", value = "owner"))) + field.add(UICol() + .add(UIReadOnlyField("deadline", label = "deadline", value = "deadline"))) - LayoutUtils.process(layout) + field.add(UIRow().add(UICol().add(UILabel(""" Anschließend werden die Fragen der Umfrage angelegt. + Die Fragen können aus verschiedenen Typen bestehen. + """)))) + + layout.add(field) + + layout.add(UIFieldset().add(UILabel("YesNoQuestion")).add( + UICol() + .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage die mit Ja oder Nein Beantwortet werden kann")) + )) + layout.add(UIFieldset().add(UILabel("MultipleChoiceQuestion")).add( + UICol() + .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage die mit mehreren Antworten Beantwortet werden kann")) + )) + layout.add(UIFieldset().add(UILabel("TextQuestion")).add( + UICol() + .add(UIReadOnlyField("question", label = "Question", value = """Eine Frage die mit einer Freitext Antwort Beantwortet werden kann""".trimMargin())) + + )) + layout.add(UIFieldset().add(UILabel("DateQuestion")).add( + UICol() + .add(UIReadOnlyField("question", label = "Question", + value = """Eine Frage ob an einem Tag (Uhrzeit) die Teilnehmer zeit haben. Die einem Ja, Nein oder Vielleicht + Beantwortet werden kann""".trimMargin() + )))) + layout.add(UIFieldset().add(UILabel("Dropdown")).add( + UICol() + .add(UIReadOnlyField("question", label = "Question", value = """ Eine Frage die mit einem Dropdown Beantwortet werden + | kann""".trimMargin() + )))) - val data = Data( - user = username, - password = authenticationsService.getToken(ThreadLocalUserContext.userId!!, UserTokenType.DAV_TOKEN), - server = request.serverName, - serverPath = "/users/${username}/addressBooks/default", - ios = "CardDAV account", - thunderbird = "CardDAV account" + layout.add( + UIRow().add( + UIButton.createBackButton( + responseAction = ResponseAction( + PagesResolver.getEditPageUrl( + PollPageRest::class.java, + absolute = true, + ), targetType = TargetType.REDIRECT + ), + default = true + ) + ) ) - return FormLayoutData(data, layout, null) + LayoutUtils.process(layout) + + return FormLayoutData(null, layout, createServerData(request)) + + } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 57c7f45c26..19aa126c12 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -1,13 +1,11 @@ package org.projectforge.rest.poll import com.fasterxml.jackson.databind.ObjectMapper -import org.json.simple.JSONObject import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType -import org.projectforge.rest.CardDAVInfoPageRest import org.projectforge.rest.VacationExportPageRest import org.projectforge.rest.config.Rest import org.projectforge.rest.core.AbstractDTOPagesRest @@ -73,8 +71,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val obj = PollDO() dto.copyTo(obj) val layout = super.createEditLayout(dto, userAccess) - layout.add(MenuItem("zt", "moin", url = PagesResolver.getDynamicPageUrl(PollInfoPageRest::class.java), type = MenuItemTargetType - .MODAL)) + layout.add(MenuItem("pollDirections", "Description", url = PagesResolver.getDynamicPageUrl(PollInfoPageRest::class.java), type = + MenuItemTargetType.MODAL)) layout.add( UIRow().add( UIFieldset(UILength(md = 6, lg = 4)).add(lc, "title", "description", "location", "owner", "deadline") @@ -107,11 +105,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun onWatchFieldsUpdate( request: HttpServletRequest, dto: Poll, watchFieldsTriggered: Array? ): ResponseEntity { - val title = dto.title - val description = dto.description - val location = dto.location - val deadline = dto.deadline - val userAccess = UILayout.UserAccess() val poll = PollDO() From 5c5ecfbcaf378a12da634434fb5b3318b36f9aa6 Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Wed, 19 Apr 2023 14:01:15 +0200 Subject: [PATCH 034/160] access and attendees, pre merge with dev --- .../framework/persistence/api/BaseDao.java | 1 - .../org/projectforge/business/poll/PollDO.kt | 10 +- .../org/projectforge/business/poll/PollDao.kt | 49 +++++++--- .../migrate/common/V7.5.1.8__7.5.1.0-POLL.sql | 3 + .../kotlin/org/projectforge/rest/poll/Poll.kt | 9 +- .../projectforge/rest/poll/PollPageRest.kt | 91 +++++-------------- 6 files changed, 77 insertions(+), 86 deletions(-) create mode 100644 projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.8__7.5.1.0-POLL.sql diff --git a/projectforge-business/src/main/java/org/projectforge/framework/persistence/api/BaseDao.java b/projectforge-business/src/main/java/org/projectforge/framework/persistence/api/BaseDao.java index caeb8548ec..8078e349ea 100644 --- a/projectforge-business/src/main/java/org/projectforge/framework/persistence/api/BaseDao.java +++ b/projectforge-business/src/main/java/org/projectforge/framework/persistence/api/BaseDao.java @@ -1115,7 +1115,6 @@ public boolean hasInsertAccess(final PFUserDO user) { public boolean hasLoggedInUserUpdateAccess(final O obj, final O dbObj, final boolean throwException) { return hasUpdateAccess(ThreadLocalUserContext.getUser(), obj, dbObj, throwException); } - /** * Checks update access right by calling hasAccess(obj, OperationType.UPDATE). * diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 066646503c..f3b5c371a3 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -25,7 +25,7 @@ open class PollDO : BaseUserGroupRightsDO() { @get:Column(name = "description", nullable = true, length = 10000) open var description: String? = null - @get:PropertyInfo(i18nKey = "poll.owner") + @get:PropertyInfo(i18nKey = "poll.owner", additionalI18nKey = "poll.owner.explaination") @get:ManyToOne(fetch = FetchType.LAZY) @get:JoinColumn(name = "owner_pk") override var owner: PFUserDO? = null @@ -41,4 +41,12 @@ open class PollDO : BaseUserGroupRightsDO() { @PropertyInfo(i18nKey = "poll.date") @get:Column(name = "date", nullable = true) open var date: LocalDate? = null + + @PropertyInfo(i18nKey = "poll.attendees") + @get:Column(name = "attendeesIds", nullable = true) + open var attendeesIds: String? = null + + @PropertyInfo(i18nKey = "poll.group_attendees") + @get:Column(name = "groupAttendeesIds", nullable = true) + open var groupAttendeesIds: String? = null } \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index 89d2c5d112..562c50530e 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -1,15 +1,20 @@ package org.projectforge.business.poll +import org.projectforge.business.group.service.GroupService +import org.projectforge.common.i18n.UserException import org.projectforge.framework.access.OperationType import org.projectforge.framework.persistence.api.BaseDao -import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext.user import org.projectforge.framework.persistence.user.entities.PFUserDO +import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Repository @Repository open class PollDao : BaseDao(PollDO::class.java){ + @Autowired + private val groupService: GroupService? = null + override fun newInstance(): PollDO { return PollDO() } @@ -21,27 +26,43 @@ open class PollDao : BaseDao(PollDO::class.java){ operationType: OperationType?, throwException: Boolean ): Boolean { - /*val loggedInUserId = ThreadLocalUserContext.user?.id - if (obj == null) { + if (obj == null && operationType == OperationType.SELECT) { return true + }; + if (obj != null && operationType == OperationType.SELECT){ + if(hasFullAccess(obj) || isAttendee(obj)) + return true + } + if(obj != null) { + return hasFullAccess(obj) } - if (obj.fullAccessUserIds!!.contains(loggedInUserId.toString())) { + return false + } + fun hasFullAccess(obj: PollDO): Boolean { + val loggedInUser = user + if(!obj.fullAccessUserIds.isNullOrBlank() && obj.fullAccessUserIds!!.contains(loggedInUser?.id.toString())) return true - }*/ - if (operationType == OperationType.SELECT){ + if(obj.owner?.id == loggedInUser?.id) return true - }; + if (!obj.fullAccessGroupIds.isNullOrBlank()){ + val groupIdArray = obj.fullAccessGroupIds!!.split(", ").map { it.toInt() }.toIntArray() + val groupUsers = groupService?.getGroupUsers(groupIdArray) + if(groupUsers?.contains(loggedInUser) == true) + return true + } return false } - - override fun onSaveOrModify(obj: PollDO) { + fun isAttendee(obj: PollDO): Boolean { val loggedInUser = user - val userString = obj.fullAccessUserIds - if (loggedInUser != null && !obj.fullAccessUserIds!!.contains(loggedInUser.id.toString())) { - obj.fullAccessUserIds = userString.plus(", ${loggedInUser.id}"); + if (!obj.attendeesIds.isNullOrBlank() && obj.attendeesIds!!.split(", ").contains(loggedInUser?.id.toString())) + return true + if (!obj.groupAttendeesIds.isNullOrBlank()){ + val groupIdArray = obj.groupAttendeesIds!!.split(", ").map { it.toInt() }.toIntArray() + val groupUsers = groupService?.getGroupUsers(groupIdArray) + if(groupUsers?.contains(loggedInUser) == true) + return true } - // Prüfen, ob loggedInUser in accessUsers, wenn nicht, hinuif+gen - super.onSaveOrModify(obj) + return false } } \ No newline at end of file diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.8__7.5.1.0-POLL.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.8__7.5.1.0-POLL.sql new file mode 100644 index 0000000000..8003c80706 --- /dev/null +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.8__7.5.1.0-POLL.sql @@ -0,0 +1,3 @@ +ALTER TABLE T_POLL + ADD COLUMN attendeesIds VARCHAR(5000), + ADD COLUMN groupAttendeesIds VARCHAR(5000); \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index 427d29183a..d82be0a06f 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -15,18 +15,23 @@ class Poll( var date: LocalDate? = null, var deadline: LocalDate? = null, var fullAccessGroups: List? = null, - var fullAccessUsers: List? = null + var fullAccessUsers: List? = null, + var groupAttendees: List? = null, + var attendees: List? = null ) : BaseDTO() { override fun copyFrom(src: PollDO) { super.copyFrom(src) fullAccessGroups = Group.toGroupList(src.fullAccessGroupIds) fullAccessUsers = User.toUserList(src.fullAccessUserIds) + groupAttendees = Group.toGroupList(src.groupAttendeesIds) + attendees = User.toUserList(src.attendeesIds) } - // The user and group ids are stored as csv list of integers in the data base. override fun copyTo(dest: PollDO) { super.copyTo(dest) dest.fullAccessGroupIds = Group.toIntList(fullAccessGroups) dest.fullAccessUserIds = User.toIntList(fullAccessUsers) + dest.groupAttendeesIds = Group.toIntList(groupAttendees) + dest.attendeesIds = User.toIntList(attendees) } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index a4972b5b79..6c1db560a1 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -1,21 +1,22 @@ package org.projectforge.rest.poll +import org.projectforge.business.fibu.EmployeeDO +import org.projectforge.business.group.service.GroupService import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao -import org.projectforge.business.user.service.UserPrefService import org.projectforge.business.user.service.UserService +import org.projectforge.business.vacation.model.VacationStatus import org.projectforge.framework.persistence.api.MagicFilter +import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext +import org.projectforge.framework.utils.NumberHelper import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType import org.projectforge.rest.VacationExportPageRest import org.projectforge.rest.config.Rest import org.projectforge.rest.core.* -import org.projectforge.rest.dto.Group -import org.projectforge.rest.dto.User +import org.projectforge.rest.dto.* import org.projectforge.ui.* import org.springframework.beans.factory.annotation.Autowired -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import javax.servlet.http.HttpServletRequest @@ -27,6 +28,15 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @Autowired private lateinit var userService: UserService; + @Autowired + private lateinit var groupService: GroupService; + + override fun newBaseDTO(request: HttpServletRequest?): Poll { + val result = Poll() + result.owner = ThreadLocalUserContext.user + return result + } + override fun transformForDB(dto: Poll): PollDO { val pollDO = PollDO() dto.copyTo(pollDO) @@ -37,6 +47,9 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val poll = Poll() poll.copyFrom(obj) User.restoreDisplayNames(poll.fullAccessUsers, userService) + Group.restoreDisplayNames(poll.fullAccessGroups, groupService) + User.restoreDisplayNames(poll.attendees, userService) + Group.restoreDisplayNames(poll.groupAttendees, groupService) return poll } @@ -71,37 +84,13 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. UIRow() .add( UIFieldset(UILength(md = 6, lg = 4)) - .add(lc, "title", "description", "location", "owner", "deadline", "date") + .add(lc, "title", "description", "location") + .add(lc, "owner") + .add(lc, "deadline", "date") .add(UISelect.createUserSelect(lc, "fullAccessUsers", true, "poll.fullAccessUsers")) .add(UISelect.createGroupSelect(lc, "fullAccessGroups", true, "poll.fullAccessGroups")) - /*.add( - UIFieldset(UILength(md = 12, lg = 12), title = "access.title.heading") - .add( - UIRow() - .add( - UIFieldset(6, title = "access.users") - .add(UISelect.createUserSelect(lc, "fullAccessUsers", true, "plugins.banking.account.fullAccess")) - .add( - UISelect.createUserSelect( - lc, - "readonlyAccessUsers", - true, - "plugins.banking.account.readonlyAccess" - ) - ) - ) - .add( - UIFieldset(6, title = "access.groups") - .add(UISelect.createGroupSelect(lc, "fullAccessGroups", true, "plugins.banking.account.fullAccess")) - .add( - UISelect.createGroupSelect( - lc, - "readonlyAccessGroups", - true, - "plugins.banking.account.readonlyAccess" - ) - ) - )*/ + .add(UISelect.createUserSelect(lc, "attendees", true, "poll.attendees")) + .add(UISelect.createGroupSelect(lc, "groupAttendees", true, "poll.groupAttendees")) )) .add(UIButton.createAddButton(responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST))) layout.watchFields.addAll( @@ -113,44 +102,10 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. "date" ) ) - updateStats(dto) return LayoutUtils.processEditPage(layout, dto, this) } - override fun onWatchFieldsUpdate( - request: HttpServletRequest, - dto: Poll, - watchFieldsTriggered: Array? - ): ResponseEntity { - val title = dto.title - val description = dto.description - val location = dto.location - val deadline = dto.deadline - val date = dto.date - updateStats(dto) - val userAccess = UILayout.UserAccess() - val poll = PollDO() - dto.copyTo(poll) - checkUserAccess(poll, userAccess) - return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE) - .addVariable("data", dto) - .addVariable("ui", createEditLayout(dto, userAccess)) - ) - } - - private fun updateStats(dto: Poll) { - - val title = dto.title - val description = dto.description - val location = dto.location - val deadline = dto.deadline - val date = dto.date - - val pollDO = PollDO() - dto.copyTo(pollDO) - } /*dto.inputFields?.forEachIndexed { field, index -> From ad2f786e844716f5ea3af4e3eac089329c582697 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 19 Apr 2023 14:33:04 +0200 Subject: [PATCH 035/160] reformat PollPageRest --- .../projectforge/rest/poll/PollPageRest.kt | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 19aa126c12..ef488b05c6 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -6,6 +6,7 @@ import org.projectforge.business.poll.PollDao import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType +import org.projectforge.rest.TokenInfoPageRest import org.projectforge.rest.VacationExportPageRest import org.projectforge.rest.config.Rest import org.projectforge.rest.core.AbstractDTOPagesRest @@ -26,7 +27,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun transformForDB(dto: Poll): PollDO { val pollDO = PollDO() dto.copyTo(pollDO) - if(dto.inputFields!= null){ + if (dto.inputFields != null) { pollDO.inputFields = ObjectMapper().writeValueAsString(dto.inputFields) } return pollDO @@ -37,7 +38,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun transformFromDB(obj: PollDO, editMode: Boolean): Poll { val poll = Poll() poll.copyFrom(obj) - if(obj.inputFields!= null){ + if (obj.inputFields != null) { var a = ObjectMapper().readValue(obj.inputFields, MutableList::class.java) poll.inputFields = a.map { Frage().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() } @@ -65,14 +66,25 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } - override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { val lc = LayoutContext(PollDO::class.java) val obj = PollDO() dto.copyTo(obj) val layout = super.createEditLayout(dto, userAccess) - layout.add(MenuItem("pollDirections", "Description", url = PagesResolver.getDynamicPageUrl(PollInfoPageRest::class.java), type = - MenuItemTargetType.MODAL)) + layout.add( + MenuItem( + "pollDirections", "Description", url = PagesResolver.getDynamicPageUrl(PollInfoPageRest::class.java), type = + MenuItemTargetType.MODAL + ) + ) + .add( + UIButton.createLinkButton( + id = "pollDirections", + responseAction = ResponseAction( + targetType = TargetType.MODAL + ) + ) + ) layout.add( UIRow().add( UIFieldset(UILength(md = 6, lg = 4)).add(lc, "title", "description", "location", "owner", "deadline") @@ -114,8 +126,9 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) } + @GetMapping("anleitung") - fun rendernleitung():UILayout{ + fun rendernleitung(): UILayout { val layout = UILayout("poll.anleitung") layout.add( UIRow().add( @@ -160,10 +173,10 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } var frage = Frage(uid = UUID.randomUUID().toString(), type = type) - if(type== BaseType.JaNeinFrage) { + if (type == BaseType.JaNeinFrage) { frage.antworten = mutableListOf("ja", "nein") } - if(type== BaseType.DatumsAbfrage) { + if (type == BaseType.DatumsAbfrage) { frage.antworten = mutableListOf("Ja", "Vielleicht", "Nein") } From 9e1d8ad78649fba9714c7b0bba2cacc0767dee07 Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Wed, 19 Apr 2023 15:51:00 +0200 Subject: [PATCH 036/160] removed unnecessary method --- .../projectforge/rest/poll/PollPageRest.kt | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index d105645443..7a0c0663e6 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -61,8 +61,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val poll = Poll() poll.copyFrom(pollDO) if (pollDO.inputFields != null) { - var a = ObjectMapper().readValue(pollDO.inputFields, MutableList::class.java) - poll.inputFields = a.map { Question().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() + val fields = ObjectMapper().readValue(pollDO.inputFields, MutableList::class.java) + poll.inputFields = fields.map { Question().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() } User.restoreDisplayNames(poll.fullAccessUsers, userService) Group.restoreDisplayNames(poll.fullAccessGroups, groupService) @@ -140,27 +140,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return LayoutUtils.processEditPage(layout, dto, this) } - //TODO refactor this whole file into multiple smaller files - override fun onWatchFieldsUpdate( - request: HttpServletRequest, dto: Poll, watchFieldsTriggered: Array? - ): ResponseEntity { - val title = dto.title - val description = dto.description - val location = dto.location - val deadline = dto.deadline - - val userAccess = UILayout.UserAccess() - val poll = PollDO() - dto.copyTo(poll) - checkUserAccess(poll, userAccess) - return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) - ) - } - - override fun onAfterSaveOrUpdate(request: HttpServletRequest, poll: PollDO, postData: PostData) { super.onAfterSaveOrUpdate(request, poll, postData) val dto = postData.data From 3ac86eed5d67f4dcff7186a437625d002e686ac9 Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Wed, 19 Apr 2023 16:39:47 +0200 Subject: [PATCH 037/160] added menu item --- .../main/kotlin/org/projectforge/menu/builder/MenuCreator.kt | 2 ++ .../main/kotlin/org/projectforge/menu/builder/MenuItemDefId.kt | 1 + 2 files changed, 3 insertions(+) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuCreator.kt b/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuCreator.kt index c7d60e11b3..c86ef61179 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuCreator.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuCreator.kt @@ -36,6 +36,7 @@ import org.projectforge.business.orga.ContractDao import org.projectforge.business.orga.PostausgangDao import org.projectforge.business.orga.PosteingangDao import org.projectforge.business.orga.VisitorbookDao +import org.projectforge.business.poll.PollDao import org.projectforge.business.sipgate.SipgateConfiguration import org.projectforge.business.user.ProjectForgeGroup import org.projectforge.business.user.UserRightValue @@ -449,6 +450,7 @@ open class MenuCreator { requiredUserRightId = ContractDao.USER_RIGHT_ID, requiredUserRightValues = READONLY_READWRITE ) ) + .add(MenuItemDef(MenuItemDefId.POLL)) .add( MenuItemDef( MenuItemDefId.VISITORBOOK, diff --git a/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuItemDefId.kt b/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuItemDefId.kt index 677f89bcb5..b105dbdc39 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuItemDefId.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuItemDefId.kt @@ -86,6 +86,7 @@ enum class MenuItemDefId constructor(val i18nKey: String, val url: String? = nul OUTGOING_INVOICE_LIST("menu.fibu.rechnungen", "wa/outgoingInvoiceList"), // PERSONAL_STATISTICS("menu.personalStatistics", "wa/personalStatistics"), // PHONE_CALL("menu.phoneCall", "wa/phoneCall"), // + POLL("menu.poll", getReactListUrl("poll")), // PROJECT_LIST("menu.fibu.projekte", getReactListUrl("project")), // REPORT_OBJECTIVES("menu.fibu.reporting.reportObjectives", "wa/reportObjectives"), // SEND_SMS("menu.sendSms", "wa/sendSms"), // From b62e549853f2c2fd45d682094b67fe145937399e Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 19 Apr 2023 17:07:23 +0200 Subject: [PATCH 038/160] Anleitung in Modal --- .../rest/poll/PollInfoPageRest.kt | 13 ------ .../projectforge/rest/poll/PollPageRest.kt | 40 +++++++++++-------- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt index b6f65b3b2e..cbd53bba2e 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt @@ -66,19 +66,6 @@ class PollInfoPageRest: AbstractDynamicPageRest() { | kann""".trimMargin() )))) - layout.add( - UIRow().add( - UIButton.createBackButton( - responseAction = ResponseAction( - PagesResolver.getEditPageUrl( - PollPageRest::class.java, - absolute = true, - ), targetType = TargetType.REDIRECT - ), - default = true - ) - ) - ) LayoutUtils.process(layout) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index ef488b05c6..b51ff2d8dc 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -6,7 +6,6 @@ import org.projectforge.business.poll.PollDao import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType -import org.projectforge.rest.TokenInfoPageRest import org.projectforge.rest.VacationExportPageRest import org.projectforge.rest.config.Rest import org.projectforge.rest.core.AbstractDTOPagesRest @@ -73,15 +72,21 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val layout = super.createEditLayout(dto, userAccess) layout.add( MenuItem( - "pollDirections", "Description", url = PagesResolver.getDynamicPageUrl(PollInfoPageRest::class.java), type = - MenuItemTargetType.MODAL + "pollDirections", + "Description", + url = ResponseAction( + PagesResolver.getDynamicPageUrl( + PollInfoPageRest::class.java, absolute = true + ), targetType = TargetType.MODAL + ).toString() ) - ) - .add( + ).add( UIButton.createLinkButton( - id = "pollDirections", + id="pollDirections", responseAction = ResponseAction( - targetType = TargetType.MODAL + PagesResolver.getDynamicPageUrl( + PollInfoPageRest::class.java, absolute = true + ), targetType = TargetType.MODAL ) ) ) @@ -203,8 +208,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) } feld.add( - UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add(UIInput("inputFields[${index}].question")).add - (groupLayout) + UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add(UIInput("inputFields[${index}].question")) + .add(groupLayout) ) } @@ -215,8 +220,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } if (field.type == BaseType.MultipleChoices || field.type == BaseType.DropDownFrage) { - val f = UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()) - .add(UIInput("inputFields[${index}].question", label = "Die Frage")) + val f = UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add( + UIInput( + "inputFields[${index}].question", + label = "Die Frage" + ) + ) field.antworten?.forEachIndexed { i, _ -> f.add(UIInput("inputFields[${index}].antworten[${i}]", label = "AntwortMöglichkeit ${i + 1}")) } @@ -230,8 +239,9 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. if (field.type == BaseType.MultipleChoices) { f.add( UIInput( - "inputFields[${index}].numberOfSelect", dataType = UIDataType.INT, label = "Wie viele Sollen " + - "angeklickt werden können " + "inputFields[${index}].numberOfSelect", + dataType = UIDataType.INT, + label = "Wie viele Sollen " + "angeklickt werden können " ) ) } @@ -241,11 +251,9 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. feld.add( UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add( UIInput( - "inputFields[${index}].question", - label = "Hast du am ... Zeit?" + "inputFields[${index}].question", label = "Hast du am ... Zeit?" ) ) - ) } layout.add(feld) From 2c9037a19d8fac53fdd63230d41552ec5d5d2f3f Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 19 Apr 2023 18:49:21 +0200 Subject: [PATCH 039/160] fixe some things(owner name load after add field) --- .../org/projectforge/business/poll/PollDO.kt | 6 +++-- .../src/main/resources/application.properties | 4 +-- .../migrate/common/V7.5.1.2__7.5.1.0-POLL.sql | 4 +-- .../projectforge/rest/poll/PollPageRest.kt | 25 ++++++++++++------- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 8da63bb389..9ba8aef83e 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -1,6 +1,7 @@ package org.projectforge.business.poll import org.hibernate.search.annotations.Indexed +import org.hibernate.search.annotations.IndexedEmbedded import org.projectforge.business.common.BaseUserGroupRightsDO import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.persistence.api.AUserRightId @@ -27,9 +28,10 @@ open class PollDO : DefaultBaseDO() { open var description: String? = null @get:PropertyInfo(i18nKey = "poll.owner", additionalI18nKey = "poll.owner.explaination") + @IndexedEmbedded(depth = 1) @get:ManyToOne(fetch = FetchType.LAZY) - @get:JoinColumn(name = "owner_pk", nullable = false) - var owner: PFUserDO? = null + @get:JoinColumn(name = "owner_fk", nullable = false) + open var owner: PFUserDO? = null @PropertyInfo(i18nKey = "poll.location") @get:Column(name = "location") diff --git a/projectforge-business/src/main/resources/application.properties b/projectforge-business/src/main/resources/application.properties index 782c9785d0..3dfed6b30d 100644 --- a/projectforge-business/src/main/resources/application.properties +++ b/projectforge-business/src/main/resources/application.properties @@ -247,9 +247,9 @@ mail.session.pfmailsession.standardEmailSender=sender@yourserver.org #Mail protocol: Plain, StartTLS,SSL mail.session.pfmailsession.encryption=Plain #Hostname of the email server -mail.session.pfmailsession.smtp.host=mail.yourserver.org +mail.session.pfmailsession.smtp.host=localhost #Port number of the email server -mail.session.pfmailsession.smtp.port=25 +mail.session.pfmailsession.smtp.port=1025 #The email server needs authentification mail.session.pfmailsession.smtp.auth=false #Authentification by user name diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql index 0f2c9a8fa5..5005a6f510 100644 --- a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql @@ -9,7 +9,7 @@ CREATE TABLE T_POLL title CHARACTER VARYING(1000) NOT NULL, description CHARACTER VARYING(1000), location CHARACTER VARYING(1000), - owner_pk INTEGER NOT NULL, + owner_fk INTEGER NOT NULL, deadline DATE NOT NULL, date DATE, state CHARACTER VARYING(1000) NOT NULL, @@ -23,4 +23,4 @@ CREATE TABLE T_POLL ALTER TABLE T_POLL ADD CONSTRAINT t_poll_pkey PRIMARY KEY (pk); ALTER TABLE T_POLL - ADD CONSTRAINT fk_t_poll_pf_user FOREIGN KEY (owner_pk) REFERENCES t_pf_user (pk); \ No newline at end of file + ADD CONSTRAINT fk_t_poll_pf_user FOREIGN KEY (owner_fk) REFERENCES t_pf_user (pk); \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 33eb37966b..0afb33a4eb 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -7,6 +7,7 @@ import org.projectforge.business.poll.PollDao import org.projectforge.business.user.service.UserService import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext +import org.projectforge.menu.MenuItem import org.projectforge.rest.config.Rest import org.projectforge.rest.config.RestUtils import org.projectforge.rest.core.* @@ -86,7 +87,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { - val lc = LayoutContext(PollDO::class.java) val poll = PollDO() dto.copyTo(poll) val layout = super.createEditLayout(dto, userAccess) @@ -148,12 +148,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) addQuestionFieldset(layout, dto) - layout.watchFields.addAll( - arrayOf( - "title", "description", "location", "deadline", - "date" - ) - ) return LayoutUtils.processEditPage(layout, dto, this) } @@ -173,6 +167,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ): ResponseEntity { val dto = postData.data val userAccess = UILayout.UserAccess(insert = true, update = true) + //TODO: owner is empty,why? Only id is set + if(dto.owner?.lastname == null || dto.owner?.firstname == null){ + val owner = userService.getUser(dto.owner?.id) + dto.owner?.firstname = owner.firstname + dto.owner?.lastname = owner.lastname + } val found = dto.inputFields?.find { it.uid == fieldUid } found?.answers?.add("") @@ -191,8 +191,15 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val dto = postData.data val poll = PollDO() - var type = BaseType.valueOf(dto.questionType ?: "TextQuestion") - var question = Question(uid = UUID.randomUUID().toString(), type = type) + //TODO: owner is empty,why? Only id is set + if(dto.owner?.lastname == null || dto.owner?.firstname == null){ + val owner = userService.getUser(dto.owner?.id) + dto.owner?.firstname = owner.firstname + dto.owner?.lastname = owner.lastname + } + + val type = BaseType.valueOf(dto.questionType ?: "TextQuestion") + val question = Question(uid = UUID.randomUUID().toString(), type = type) if(type == BaseType.YesNoQuestion) { question.answers = mutableListOf("ja", "nein") } From 786b652553e624a4ac5ad4dadcaaed33e4596b6e Mon Sep 17 00:00:00 2001 From: Jonas Bernst <91050812+jocrafter@users.noreply.github.com> Date: Thu, 20 Apr 2023 09:40:35 +0200 Subject: [PATCH 040/160] Change button --- .../projectforge/rest/poll/PollPageRest.kt | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 0afb33a4eb..1a120ffafc 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -90,26 +90,27 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val poll = PollDO() dto.copyTo(poll) val layout = super.createEditLayout(dto, userAccess) - layout.add( - MenuItem( - "pollDirections", - "Description", - url = ResponseAction( - PagesResolver.getDynamicPageUrl( - PollInfoPageRest::class.java, absolute = true - ), targetType = TargetType.MODAL - ).toString() - ) - ).add( - UIButton.createLinkButton( - id="pollDirections", - responseAction = ResponseAction( - PagesResolver.getDynamicPageUrl( - PollInfoPageRest::class.java, absolute = true - ), targetType = TargetType.MODAL + + layout.add( + UIRow().add( + UICol( + UILength(11) ) + ).add( + UICol( + UILength(1) + ).add( + UIButton.createLinkButton( + id = "pollDirections", responseAction = ResponseAction( + PagesResolver.getDynamicPageUrl( + PollInfoPageRest::class.java, absolute = true + ), targetType = TargetType.MODAL + ) + ) + ) ) - ) + ) + layout.add( UIRow().add( UIFieldset(UILength(md = 6, lg = 4)).add(lc, "title", "description", "location") From cea47b9e356108cf14571a06d58d57dfc9bb5aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Thu, 20 Apr 2023 10:52:27 +0200 Subject: [PATCH 041/160] Added delete button for questions and some overall layout design changes --- .../projectforge/rest/poll/PollPageRest.kt | 158 ++++++++++-------- .../rest/poll/types/PremadeQuestions.kt | 13 +- .../projectforge/rest/poll/types/Question.kt | 4 +- 3 files changed, 101 insertions(+), 74 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 69b3a48318..a19e5a9398 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -77,36 +77,45 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val poll = PollDO() dto.copyTo(poll) val layout = super.createEditLayout(dto, userAccess) - layout.add( - UIRow().add( - UIFieldset(UILength(md = 6, lg = 4)).add(lc, "title", "description", "location", "owner", "deadline") - ) - ) - layout.add( - UIRow().add( - UIFieldset(UILength(md = 6, lg = 4)).add( - UIButton.createDefaultButton( - id = "add-question-button", - responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST), - title = "Eigene Frage hinzufügen" + + val fieldset = UIFieldset(UILength(12)) + fieldset + .add(lc, "title", "description", "location", "owner", "deadline") + .add( + UIRow() + .add( + UICol(UILength(xs = 9, sm = 9, md = 9, lg = 9)) + .add(UISelect("questionType", values = BaseType.values().map { UISelectValue(it, it.name) }, label = "questionType")) + ) + .add( + UICol(UILength(xs = 3, sm = 3, md = 3, lg = 3)) + .add( + UIButton.createDefaultButton( + id = "add-question-button", + responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST), + title = "Eigene Frage hinzufügen" + ) + ) ) - ).add( - UISelect("questionType", values = BaseType.values().map { UISelectValue(it, it.name) }) - ) ) - ) - - layout.add( - UIRow().add( - UIFieldset(UILength(md = 6, lg = 4)).add( - UIButton.createDefaultButton( - id = "micromata-vorlage-button", - responseAction = ResponseAction("${Rest.URL}/poll/addPremadeQuestions", targetType = TargetType.POST), - title = "Micromata Vorlage nutzen" + .add( + UIRow() + .add( + UICol(UILength(xs = 9, sm = 9, md = 9, lg = 9)) + ) + .add( + UICol(UILength(xs = 3, sm = 3, md = 3, lg = 3)) + .add( + UIButton.createDefaultButton( + id = "micromata-vorlage-button", + responseAction = ResponseAction("${Rest.URL}/poll/addPremadeQuestions", targetType = TargetType.POST), + title = "Micromata Vorlage nutzen" + ) + ) ) - ) ) - ) + + layout.add(fieldset) addQuestionFieldset(layout, dto) @@ -120,7 +129,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } - //TODO refactor this whole file into multiple smaller files override fun onWatchFieldsUpdate( request: HttpServletRequest, dto: Poll, watchFieldsTriggered: Array? @@ -147,8 +155,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } - @PostMapping("/addAntwort/{fieldId}") - fun addAntwortFeld( + @PostMapping("/addAnswer/{fieldId}") + fun addAnswerForMultipleChoice( @RequestBody postData: PostData, @PathVariable("fieldId") fieldUid: String, ): ResponseEntity { @@ -213,68 +221,95 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. private fun addQuestionFieldset(layout: UILayout, dto: Poll) { dto.inputFields?.forEachIndexed { index, field -> - val row = UIRow() + val fieldset = UIFieldset(UILength(12), title = field.type.toString()) + .add(generateDeleteButton(layout, field.uid)) + .add(UIInput("inputFields[${index}].question", label = "Frage")) if (field.type == BaseType.YesNoQuestion) { - val groupLayout = UIGroup() + val buttons = UIGroup() field.answers?.forEach { answer -> - groupLayout.add( + buttons.add( UIRadioButton( "YesNoQuestion[${index}].question", answer, label = answer ) ) } - row.add( - UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add(UIInput("inputFields[${index}].question")).add - (groupLayout) - ) - } - - if (field.type == BaseType.TextQuestion) { - row.add( - UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add(UIInput("inputFields[${index}].question")) - ) + fieldset + .add(buttons) } - if (field.type == BaseType.MultipleChoices || field.type == BaseType.DropDownQuestion) { - val f = UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()) - .add(UIInput("inputFields[${index}].question", label = "Die Frage")) + if (field.type == BaseType.MultipleChoices) { + val groupLayout = UIGroup() field.answers?.forEachIndexed { i, _ -> - f.add(UIInput("inputFields[${index}].answers[${i}]", label = "Antwortmöglichkeit ${i + 1}")) + groupLayout.add(UIInput("inputFields[${index}].answers[${i}]", label = "Antwortmöglichkeit ${i + 1}")) } - f.add( + groupLayout.add( UIButton.createAddButton( responseAction = ResponseAction( - "${Rest.URL}/poll/addAntwort/${field.uid}", targetType = TargetType.POST + "${Rest.URL}/poll/addAnswer/${field.uid}", targetType = TargetType.POST ) ) ) if (field.type == BaseType.MultipleChoices) { - f.add( + groupLayout.add( UIInput( "inputFields[${index}].numberOfSelect", dataType = UIDataType.INT, label = "Wie viele sollen " + "angeklickt werden können?" ) ) } - row.add(f) + fieldset.add(groupLayout) } if (field.type == BaseType.DateQuestion) { - row.add( - UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add( + fieldset + .add( UIInput( "inputFields[${index}].question", label = "Hast du am ... Zeit?" ) ) - - ) } - layout.add(row) + layout.add(fieldset) } } + private fun generateDeleteButton(layout: UILayout, uid:String?):UIRow { + val row = UIRow() + row.add( + UICol(UILength(11)) + ) + .add( + UICol(length = UILength(1)) + .add( + UIButton.createDangerButton( + id = "X", + responseAction = ResponseAction( + "${Rest.URL}/poll/deleteQuestion/${uid}", targetType = TargetType.POST + ) + ).withConfirmMessage(layout, confirmMessage = "Willst du wirklich diese Frage löschen?")) + ) + return row + } + + @PostMapping("/deleteQuestion/{uid}") + fun removeQuestion( + @RequestBody postData: PostData, + @PathVariable("uid") uid: String, + ): ResponseEntity { + val dto = postData.data + val userAccess = UILayout.UserAccess(insert = true, update = true) + val poll = PollDO() + + val matchingQuestion: Question? = dto.inputFields?.find { it.uid.equals(uid) } + dto.inputFields?.remove(matchingQuestion) + + dto.copyTo(poll) + return ResponseEntity.ok( + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ) + } + @PostMapping("Export") fun export(request: HttpServletRequest,poll: Poll) : ResponseEntity? { @@ -290,17 +325,4 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return RestUtils.downloadFile(filename, bytes) } - // create a update layout funktion, welche das layout nummr updatet und zurück gibt es soll für jeden Frage Basistyp eine eigene funktion haben - - - /*dto.inputFields?.forEachIndexed { field, index -> - if (field.type == msc) { - layout.add() // - "type[$index]" - Id: name - } - } - layout.add(UIRow().add(UIFieldset(UILength(md = 6, lg = 4)) - .add(lc, "name"))) - */ } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt index 7e5f69784c..623adf7360 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt @@ -1,34 +1,41 @@ package org.projectforge.rest.poll.types +import java.util.UUID + val PREMADE_QUESTIONS = mapOf( "HAS_FOOD" to Question( + uid = UUID.randomUUID().toString(), question = "Was willst du essen?", - type = BaseType.MultipleChoices, - answers = mutableListOf("Fleisch", "Vegetarisch", "Vegan"), - numberOfSelect = 1 + type = BaseType.YesNoQuestion, + answers = mutableListOf("Fleisch", "Vegetarisch", "Vegan") ), "IS_REMOTE" to Question( + uid = UUID.randomUUID().toString(), question = "Nimmst du remote teil?", type = BaseType.YesNoQuestion, answers = mutableListOf("Ja", "Nein") ), "CAN_HAVE_COMPANIONS" to Question( + uid = UUID.randomUUID().toString(), question = "Nimmst du eine Begleitung mit? (Name der Begleitung)", type = BaseType.TextQuestion, answers = mutableListOf("") ), "CAN_HAVE_CHILDREN" to Question( + uid = UUID.randomUUID().toString(), question = "Nimmst du ein Kind mit? (Name der Begleitung)", type = BaseType.TextQuestion, answers = mutableListOf("") ), "CAN_STAY_OVERNIGHT" to Question( + uid = UUID.randomUUID().toString(), question = "Willst du dort übernachten?", type = BaseType.YesNoQuestion, answers = mutableListOf("Ja", "Nein") ), "HAS_BREAKFAST" to Question( + uid = UUID.randomUUID().toString(), question = "Willst du am nächsten Tag frühstücken?", type = BaseType.YesNoQuestion, answers = mutableListOf("Ja", "Nein") diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt index 388f6bd272..9e12f88192 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt @@ -24,7 +24,5 @@ enum class BaseType { YesNoQuestion, DateQuestion, MultipleChoices, - TextQuestion, - DropDownQuestion, - PremadeQuestion + TextQuestion } \ No newline at end of file From f7d7fe58af98e49527c2a210007555d5b613eace Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Thu, 20 Apr 2023 11:32:53 +0200 Subject: [PATCH 042/160] Add PollResponsePage, database table and all data objects. --- .../business/poll/PollResponseDO.kt | 31 ++++ .../business/poll/PollResponseDao.kt | 23 +++ .../V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql | 24 ++- .../projectforge/rest/poll/PollPageRest.kt | 9 +- .../projectforge/rest/poll/PollResponse.kt | 42 +++++ .../rest/poll/ResponsePageRest.kt | 162 +++++++++++++----- .../rest/poll/types/PollResponse.kt | 4 - .../rest/poll/types/PollResponseDO.kt | 4 - 8 files changed, 238 insertions(+), 61 deletions(-) create mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt create mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt delete mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt delete mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponseDO.kt diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt new file mode 100644 index 0000000000..449fa7f104 --- /dev/null +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt @@ -0,0 +1,31 @@ +package org.projectforge.business.poll + +import org.hibernate.search.annotations.Indexed +import org.projectforge.business.poll.PollDO +import org.projectforge.common.anots.PropertyInfo +import org.projectforge.framework.persistence.api.AUserRightId +import org.projectforge.framework.persistence.entities.DefaultBaseDO +import org.projectforge.framework.persistence.user.entities.PFUserDO +import org.springframework.context.annotation.DependsOn +import javax.persistence.* + +@Entity +@Indexed +@Table(name = "t_poll_response") +@AUserRightId(value = "poll.response", checkAccess = false) +@DependsOn("org.projectforge.framework.persistence.user.entities.PFUserDO") +open class PollResponseDO : DefaultBaseDO() { + @get:PropertyInfo(i18nKey = "poll.response.poll") + @get:ManyToOne(fetch = FetchType.LAZY) + @get:JoinColumn(name = "poll_pk", nullable = false) + open var poll: PollDO? = null + + @get:PropertyInfo(i18nKey = "poll.response.owner") + @get:ManyToOne(fetch = FetchType.LAZY) + @get:JoinColumn(name = "owner_pk", nullable = false) + open var owner: PFUserDO? = null + + @PropertyInfo(i18nKey = "poll.responses") + @get:Column(name = "responses", nullable = true, length = 1000) + open var responses: String? = null +} \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt new file mode 100644 index 0000000000..66cd987917 --- /dev/null +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt @@ -0,0 +1,23 @@ +package org.projectforge.business.poll + +import org.projectforge.framework.access.OperationType +import org.projectforge.framework.persistence.api.BaseDao +import org.projectforge.framework.persistence.user.entities.PFUserDO +import org.springframework.stereotype.Repository + +@Repository +open class PollResponseDao : BaseDao(PollResponseDO::class.java) { + override fun newInstance(): PollResponseDO { + return PollResponseDO() + } + + override fun hasAccess( + user: PFUserDO?, + obj: PollResponseDO?, + oldObj: PollResponseDO?, + operationType: OperationType?, + throwException: Boolean + ): Boolean { + return true + } +} \ No newline at end of file diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql index 7cdecaa68f..d0f060b2ba 100644 --- a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-RELEASE-POLL_TABLE.sql @@ -7,15 +7,33 @@ CREATE TABLE T_POLL created TIMESTAMP WITHOUT TIME ZONE, last_update TIMESTAMP WITHOUT TIME ZONE, title CHARACTER VARYING(1000), - description CHARACTER VARYING(1000), + description CHARACTER VARYING(10000), location CHARACTER VARYING(1000), owner_pk INTEGER NOT NULL, deadline DATE NOT NULL, - inputFields CHARACTER Varying(1000), + inputFields CHARACTER Varying(10000), date DATE ); ALTER TABLE T_POLL ADD CONSTRAINT t_poll_pkey PRIMARY KEY (pk); ALTER TABLE T_POLL - ADD CONSTRAINT fk_t_poll_pf_user FOREIGN KEY (owner_pk) REFERENCES t_pf_user (pk); \ No newline at end of file + ADD CONSTRAINT fk_t_poll_pf_user FOREIGN KEY (owner_pk) REFERENCES t_pf_user (pk); + +CREATE TABLE T_POLL_RESPONSE +( + pk INTEGER NOT NULL, + deleted BOOLEAN NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE, + last_update TIMESTAMP WITHOUT TIME ZONE, + poll_pk INTEGER NOT NULL, + owner_pk INTEGER NOT NULL, + responses CHARACTER Varying(10000) +); + +ALTER TABLE T_POLL_RESPONSE + ADD CONSTRAINT t_poll_response_pkey PRIMARY KEY (pk); +ALTER TABLE T_POLL_RESPONSE + ADD CONSTRAINT fk_t_poll_response_pf_user FOREIGN KEY (owner_pk) REFERENCES t_pf_user (pk); +ALTER TABLE T_POLL_RESPONSE + ADD CONSTRAINT fk_t_poll_response_poll FOREIGN KEY (poll_pk) REFERENCES t_poll (pk); \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index ba5cf6e739..859932cc5a 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -1,7 +1,6 @@ package org.projectforge.rest.poll import com.fasterxml.jackson.databind.ObjectMapper -import org.json.simple.JSONObject import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao import org.projectforge.framework.persistence.api.MagicFilter @@ -39,7 +38,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val poll = Poll() poll.copyFrom(obj) if(obj.inputFields!= null){ - var a = ObjectMapper().readValue(obj.inputFields, MutableList::class.java) + val a = ObjectMapper().readValue(obj.inputFields, MutableList::class.java) poll.inputFields = a.map { Frage().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() } return poll @@ -65,8 +64,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) } - - override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { val lc = LayoutContext(PollDO::class.java) val obj = PollDO() @@ -141,7 +138,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ): ResponseEntity { val userAccess = UILayout.UserAccess(insert = true, update = true) val dto = postData.data - var type = BaseType.valueOf(dto.questionType ?: "FreiTextFrage") + val type = BaseType.valueOf(dto.questionType ?: "FreiTextFrage") val poll = PollDO() @@ -149,7 +146,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.inputFields = mutableListOf() } - var frage = Frage(uid = UUID.randomUUID().toString(), type = type) + val frage = Frage(uid = UUID.randomUUID().toString(), type = type) if(type== BaseType.JaNeinFrage) { frage.antworten = mutableListOf("ja", "nein") } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt new file mode 100644 index 0000000000..a8ff1d0945 --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt @@ -0,0 +1,42 @@ +package org.projectforge.rest.poll + +import com.fasterxml.jackson.databind.ObjectMapper +import org.apache.poi.ss.formula.functions.T +import org.projectforge.business.poll.PollDO +import org.projectforge.business.poll.PollResponseDO +import org.projectforge.framework.persistence.user.entities.PFUserDO +import org.projectforge.rest.dto.BaseDTO +import org.projectforge.rest.poll.types.Frage + +class PollResponse: BaseDTO() { + var poll: PollDO? = null + var owner: PFUserDO? = null + var responses: MutableList? = mutableListOf() + + override fun copyTo(dest: PollResponseDO) { + if (!this.responses.isNullOrEmpty()) { + + + dest.responses = ObjectMapper().writeValueAsString(this.responses) + } + super.copyTo(dest) + } + + override fun copyFrom(src: PollResponseDO) { + if (!src.responses.isNullOrEmpty()) { + val a = ObjectMapper().readValue(src.responses, MutableList::class.java) + this.responses = a.map { Answer().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() + } + super.copyFrom(src) + } +} + +class Answer { + var uid: String? = null + var questionUid: String? = "" + var antworten: MutableList? = mutableListOf() + + fun toObject(string:String): Answer { + return ObjectMapper().readValue(string, Answer::class.java) + } +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 189a44e0be..47ebb021de 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -1,78 +1,152 @@ package org.projectforge.rest.poll +import com.fasterxml.jackson.databind.ObjectMapper +import org.projectforge.Constants import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao +import org.projectforge.business.poll.PollResponseDO +import org.projectforge.business.poll.PollResponseDao +import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext +import org.projectforge.framework.utils.NumberHelper import org.projectforge.rest.config.Rest import org.projectforge.rest.core.AbstractDynamicPageRest +import org.projectforge.rest.core.PagesResolver +import org.projectforge.rest.core.RestResolver import org.projectforge.rest.dto.FormLayoutData -import org.projectforge.rest.poll.types.BaseType +import org.projectforge.rest.dto.PostData +import org.projectforge.rest.poll.types.* import org.projectforge.ui.* -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam -import org.springframework.web.bind.annotation.RestController +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import java.util.UUID import javax.servlet.http.HttpServletRequest @RestController -@RequestMapping("${Rest.URL}/poll/antwort") +@RequestMapping("${Rest.URL}/antwort") class ResponsePageRest : AbstractDynamicPageRest() { + @Autowired + private lateinit var pollDao: PollDao - + @Autowired + private lateinit var pollResponseDao: PollResponseDao @GetMapping("dynamic") - fun test(request: HttpServletRequest, @RequestParam("pollId") pollId: Int?): FormLayoutData { + fun test(request: HttpServletRequest, @RequestParam("id") pollStringId: String?): FormLayoutData { + //val lc = LayoutContext(PollDO::class.java) + val id = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") + val pollData = pollDao.internalGetById(id) ?: PollDO() + val pollDto = transformPollFromDB(pollData) val layout = UILayout("poll.antwort.title") - val lc = LayoutContext(PollDO::class.java) - val data = PollDao().getById(1) - val dto = Poll() - dto.copyFrom(data) - - - layout.add( - UIRow().add( - UIFieldset(UILength(md = 6, lg = 4)).add(lc, "title", "description", "location", "owner", "deadline") - ) - ) - addQuestions(layout, dto) - - return FormLayoutData(data, layout, createServerData(request)) - } - - private fun addQuestions(layout: UILayout, dto: Poll) { - + val fieldSet = UIFieldset(12, title = pollDto.title) + fieldSet + .add(UIReadOnlyField(value = pollDto.description, label = "Description")) + .add(UIReadOnlyField(value = pollDto.location, label = "Location")) + .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = "Owner")) + .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = "Deadline")) + + layout.add(fieldSet) + + val pollResponse = PollResponse() + pollResponse.poll = pollData + + pollResponseDao.internalLoadAll().firstOrNull { response -> + response.owner == ThreadLocalUserContext.user + && response.poll?.id == pollData.id + }?.let { + pollResponse.copyFrom(it) + } + //addQuestions(layout, pollDto) + pollDto.inputFields?.forEachIndexed { index, field -> + val fieldSet2 = UIFieldset(title = field.question) + val answer = Answer() + answer.uid = UUID.randomUUID().toString() + answer.questionUid = field.uid + pollResponse.responses?.firstOrNull() { + it.questionUid==field.uid + }.let { + if (it == null) + pollResponse.responses?.add(answer) + } - dto.inputFields?.forEachIndexed { index, field -> - val feld = UIRow() - feld.add(UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString())) - .add(UICol(6).add(UILabel(field.question))) + val col = UICol() if (field.type == BaseType.FreiTextFrage) { - feld.add(UIInput("antwort",)) + col.add(UITextArea("responses[$index].antworten[0]")) } if (field.type == BaseType.JaNeinFrage) { - feld.add(UICheckbox("antwort",)) - feld.add(UICheckbox("antwort",)) + col.add(UIRadioButton("responses[$index].antworten[0]", value = field.antworten!![0], label = field.antworten?.get(0) ?: "")) + col.add(UIRadioButton("responses[$index].antworten[0]", value = field.antworten!![1], label = field.antworten?.get(1) ?: "")) } - if (field.type == BaseType.DatumsAbfrage){ - - feld.add(UICheckbox("antwort",)) - feld.add(UICheckbox("antwort",)) - feld.add(UICheckbox("antwort",)) + if (field.type == BaseType.DatumsAbfrage) { + col.add(UITextArea("responses[$index].antworten[0]")) } - if (field.type == BaseType.DropDownFrage){ - feld.add(UISelect("questionType", values = field.antworten?.map { UISelectValue(it,it) })) + if (field.type == BaseType.DropDownFrage) { + col.add(UISelect("responses[$index].antworten[0]", values = field.antworten?.map { UISelectValue(it, it) })) } - if( field.type == BaseType.MultipleChoices){ - field.antworten?.forEachIndexed{ index, s -> - feld.add(UICheckbox("antwort${index}", label =field.antworten?.get(index) ?: "")) + if (field.type == BaseType.MultipleChoices) { + //for (i in 1 until field.antworten!!.size) + //answer.antworten?.add("") + + + field.antworten?.forEachIndexed { index2, _ -> + col.add(UICheckbox("responses[$index].antworten[$index2]", label = field.antworten?.get(index2) ?: "")) } } - layout.add(feld) + fieldSet2.add(UIRow().add(col)) + layout.add(fieldSet2) } + + layout.add(UIButton.createDefaultButton( + id = "doResponse", + title = "response", + responseAction = ResponseAction( + RestResolver.getRestUrl( + this::class.java, + "doResponse" + ), targetType = TargetType.POST + ) + )) + + return FormLayoutData(pollResponse, layout, createServerData(request)) + } + + @PostMapping("doResponse") + fun doResponse( + request: HttpServletRequest, + @RequestBody postData: PostData + ): ResponseAction { + + val pollResponseDO = PollResponseDO() + postData.data.copyTo(pollResponseDO) + pollResponseDO.owner = ThreadLocalUserContext.user + + pollResponseDao.internalLoadAll().firstOrNull { pollResponse -> + pollResponse.owner == ThreadLocalUserContext.user + && pollResponse.poll?.id == postData.data.poll?.id } + ?.let { + it.responses = pollResponseDO.responses + pollResponseDao.update(it) + return ResponseAction(targetType = TargetType.REDIRECT, url = "") + } + + pollResponseDao.saveOrUpdate(pollResponseDO) + return ResponseAction(targetType = TargetType.REDIRECT, url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true)) } + + + private fun transformPollFromDB(obj: PollDO): Poll { + val poll = Poll() + poll.copyFrom(obj) + if (obj.inputFields != null) { + val a = ObjectMapper().readValue(obj.inputFields, MutableList::class.java) + poll.inputFields = a.map { Frage().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() + } + return poll + } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt deleted file mode 100644 index f8ef6ae33d..0000000000 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt +++ /dev/null @@ -1,4 +0,0 @@ -package org.projectforge.rest.poll.types - -class PollResponse { -} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponseDO.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponseDO.kt deleted file mode 100644 index 6f417fb378..0000000000 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponseDO.kt +++ /dev/null @@ -1,4 +0,0 @@ -package org.projectforge.rest.poll.types - -class PollResponseDO { -} \ No newline at end of file From 67f70b538fc99767970160abc1997f35ba33f718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Thu, 20 Apr 2023 13:32:31 +0200 Subject: [PATCH 043/160] Remade Single and MultipleResponseQuestions --- .../projectforge/rest/poll/PollPageRest.kt | 82 ++++++++++++------- .../rest/poll/types/PremadeQuestions.kt | 8 +- .../projectforge/rest/poll/types/Question.kt | 8 +- 3 files changed, 62 insertions(+), 36 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index eb451ab24c..d4dedd8a30 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -1,6 +1,7 @@ package org.projectforge.rest.poll import com.fasterxml.jackson.databind.ObjectMapper +import org.bouncycastle.asn1.x500.style.RFC4519Style.uid import org.projectforge.business.group.service.GroupService import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao @@ -187,7 +188,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. var type = BaseType.valueOf(dto.questionType ?: "TextQuestion") var question = Question(uid = UUID.randomUUID().toString(), type = type) - if(type == BaseType.YesNoQuestion) { + if(type == BaseType.SingleResponseQuestion) { question.answers = mutableListOf("ja", "nein") } if(type == BaseType.DateQuestion) { @@ -227,39 +228,21 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val fieldset = UIFieldset(UILength(12), title = field.type.toString()) .add(generateDeleteButton(layout, field.uid)) .add(UIInput("inputFields[${index}].question", label = "Frage")) - if (field.type == BaseType.YesNoQuestion) { - val buttons = UIGroup() - field.answers?.forEach { answer -> - buttons.add( - UIRadioButton( - "YesNoQuestion[${index}].question", answer, label = answer - ) - ) - } - fieldset - .add(buttons) - } - if (field.type == BaseType.MultipleChoices) { + if (field.type == BaseType.SingleResponseQuestion || field.type == BaseType.MultiResponseQuestion) { val groupLayout = UIGroup() - field.answers?.forEachIndexed { i, _ -> - groupLayout.add(UIInput("inputFields[${index}].answers[${i}]", label = "Antwortmöglichkeit ${i + 1}")) + field.answers?.forEachIndexed { answerIndex, _ -> + groupLayout.add(generateSingleAndMultiResponseAnswer(index, field.uid, answerIndex, layout)) } groupLayout.add( - UIButton.createAddButton( - responseAction = ResponseAction( - "${Rest.URL}/poll/addAnswer/${field.uid}", targetType = TargetType.POST + UIRow().add( + UIButton.createAddButton( + responseAction = ResponseAction( + "${Rest.URL}/poll/addAnswer/${field.uid}", targetType = TargetType.POST + ) ) ) ) - if (field.type == BaseType.MultipleChoices) { - groupLayout.add( - UIInput( - "inputFields[${index}].numberOfSelect", dataType = UIDataType.INT, label = "Wie viele sollen " + - "angeklickt werden können?" - ) - ) - } fieldset.add(groupLayout) } @@ -277,6 +260,48 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } } + private fun generateSingleAndMultiResponseAnswer(inputFieldIndex: Int, questionUid: String?, answerIndex: Int, layout: UILayout):UIRow { + val row = UIRow() + row.add( + UICol() + .add( + UIInput("inputFields[${inputFieldIndex}].answers[${answerIndex}]", label = "Answer ${answerIndex + 1}") + ) + ) + .add( + UICol() + .add( + UIButton.createDangerButton( + id = "X", + responseAction = ResponseAction( + "${Rest.URL}/poll/deleteAnswer/${questionUid}/${answerIndex}", targetType = TargetType.POST + ) + ).withConfirmMessage(layout, confirmMessage = "Willst du wirklich diese Antwort löschen?")) + ) + return row + } + + + @PostMapping("/deleteAnswer/{questionUid}/{answerIndex}") + fun deleteAnswerOfSingleAndMultipleResponseQuestion( + @RequestBody postData: PostData, + @PathVariable("questionUid") questionUid: String, + @PathVariable("answerIndex") answerIndex: Int + ): ResponseEntity { + val dto = postData.data + val userAccess = UILayout.UserAccess(insert = true, update = true) + val poll = PollDO() + + dto.inputFields?.find { it.uid.equals(questionUid) }?.answers?.removeAt(answerIndex) + + + dto.copyTo(poll) + return ResponseEntity.ok( + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ) + } + + private fun generateDeleteButton(layout: UILayout, uid:String?):UIRow { val row = UIRow() row.add( @@ -295,8 +320,9 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return row } + @PostMapping("/deleteQuestion/{uid}") - fun removeQuestion( + fun deleteQuestion( @RequestBody postData: PostData, @PathVariable("uid") uid: String, ): ResponseEntity { diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt index 623adf7360..d1e47339cc 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt @@ -7,13 +7,13 @@ val PREMADE_QUESTIONS = mapOf( "HAS_FOOD" to Question( uid = UUID.randomUUID().toString(), question = "Was willst du essen?", - type = BaseType.YesNoQuestion, + type = BaseType.SingleResponseQuestion, answers = mutableListOf("Fleisch", "Vegetarisch", "Vegan") ), "IS_REMOTE" to Question( uid = UUID.randomUUID().toString(), question = "Nimmst du remote teil?", - type = BaseType.YesNoQuestion, + type = BaseType.SingleResponseQuestion, answers = mutableListOf("Ja", "Nein") ), "CAN_HAVE_COMPANIONS" to Question( @@ -31,13 +31,13 @@ val PREMADE_QUESTIONS = mapOf( "CAN_STAY_OVERNIGHT" to Question( uid = UUID.randomUUID().toString(), question = "Willst du dort übernachten?", - type = BaseType.YesNoQuestion, + type = BaseType.SingleResponseQuestion, answers = mutableListOf("Ja", "Nein") ), "HAS_BREAKFAST" to Question( uid = UUID.randomUUID().toString(), question = "Willst du am nächsten Tag frühstücken?", - type = BaseType.YesNoQuestion, + type = BaseType.SingleResponseQuestion, answers = mutableListOf("Ja", "Nein") ), ) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt index 9e12f88192..085fc28a86 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt @@ -21,8 +21,8 @@ class Question( } enum class BaseType { - YesNoQuestion, - DateQuestion, - MultipleChoices, - TextQuestion + TextQuestion, + SingleResponseQuestion, + MultiResponseQuestion, + DateQuestion } \ No newline at end of file From b2085f32879f14e9177374c4f199d0a596235724 Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Thu, 20 Apr 2023 14:25:14 +0200 Subject: [PATCH 044/160] read only after creation --- .../org/projectforge/business/poll/PollDO.kt | 2 +- .../projectforge/rest/poll/PollPageRest.kt | 88 ++++++++++--------- 2 files changed, 47 insertions(+), 43 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 8da63bb389..9cd8733862 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -29,7 +29,7 @@ open class PollDO : DefaultBaseDO() { @get:PropertyInfo(i18nKey = "poll.owner", additionalI18nKey = "poll.owner.explaination") @get:ManyToOne(fetch = FetchType.LAZY) @get:JoinColumn(name = "owner_pk", nullable = false) - var owner: PFUserDO? = null + open var owner: PFUserDO? = null @PropertyInfo(i18nKey = "poll.location") @get:Column(name = "location") diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 7a0c0663e6..7495a795e7 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -102,41 +102,35 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. .add(UISelect.createGroupSelect(lc, "groupAttendees", true, "poll.groupAttendees")) ) ) - layout.add( - UIRow().add( - UIFieldset(UILength(md = 6, lg = 4)).add( - UIButton.createDefaultButton( - id = "add-question-button", - responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST), - title = "Eigene Frage hinzufügen" + if(dto.id == null){ + layout.add( + UIRow().add( + UIFieldset(UILength(md = 6, lg = 4)).add( + UIButton.createDefaultButton( + id = "add-question-button", + responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST), + title = "Eigene Frage hinzufügen" + ) + ).add( + UISelect("questionType", values = BaseType.values().map { UISelectValue(it, it.name) }) ) - ).add( - UISelect("questionType", values = BaseType.values().map { UISelectValue(it, it.name) }) ) ) - ) - - layout.add( - UIRow().add( - UIFieldset(UILength(md = 6, lg = 4)).add( - UIButton.createDefaultButton( - id = "micromata-vorlage-button", - responseAction = ResponseAction("${Rest.URL}/poll/addPremadeQuestions", targetType = TargetType.POST), - title = "Micromata Vorlage nutzen" + layout.add( + UIRow().add( + UIFieldset(UILength(md = 6, lg = 4)).add( + UIButton.createDefaultButton( + id = "micromata-vorlage-button", + responseAction = ResponseAction("${Rest.URL}/poll/addPremadeQuestions", targetType = TargetType.POST), + title = "Micromata Vorlage nutzen" + ) ) ) ) - ) + } addQuestionFieldset(layout, dto) - layout.watchFields.addAll( - arrayOf( - "title", "description", "location", "deadline", - "date" - ) - ) - return LayoutUtils.processEditPage(layout, dto, this) } @@ -186,8 +180,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } dto.inputFields!!.add(question) - dto.copyTo(poll) + dto.owner = userService.getUser(dto.owner?.id) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) @@ -215,6 +209,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. private fun addQuestionFieldset(layout: UILayout, dto: Poll) { dto.inputFields?.forEachIndexed { index, field -> + val objGiven = dto.id != null val row = UIRow() if (field.type == BaseType.YesNoQuestion) { val groupLayout = UIGroup() @@ -226,35 +221,37 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) } row.add( - UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add(UIInput("inputFields[${index}].question")).add + UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add(getUiElement(objGiven, "inputFields[${index}].question")).add (groupLayout) ) } if (field.type == BaseType.TextQuestion) { row.add( - UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add(UIInput("inputFields[${index}].question")) + UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add(getUiElement(objGiven,"inputFields[${index}].question")) ) } if (field.type == BaseType.MultipleChoices || field.type == BaseType.DropDownQuestion) { val f = UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()) - .add(UIInput("inputFields[${index}].question", label = "Die Frage")) + .add(getUiElement(objGiven, "inputFields[${index}].question", "Die Frage")) field.answers?.forEachIndexed { i, _ -> - f.add(UIInput("inputFields[${index}].answers[${i}]", label = "Antwortmöglichkeit ${i + 1}")) + f.add(getUiElement(objGiven, "inputFields[${index}].answers[${i}]", "Antwortmöglichkeit ${i + 1}")) } - f.add( - UIButton.createAddButton( - responseAction = ResponseAction( - "${Rest.URL}/poll/addAntwort/${field.uid}", targetType = TargetType.POST + if(!objGiven) { + f.add( + UIButton.createAddButton( + responseAction = ResponseAction( + "${Rest.URL}/poll/addAntwort/${field.uid}", targetType = TargetType.POST + ) ) ) - ) + } if (field.type == BaseType.MultipleChoices) { f.add( - UIInput( - "inputFields[${index}].numberOfSelect", dataType = UIDataType.INT, label = "Wie viele sollen " + - "angeklickt werden können?" + getUiElement(objGiven, + "inputFields[${index}].numberOfSelect", "Wie viele sollen angeklickt werden können?", + UIDataType.INT ) ) } @@ -264,15 +261,14 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. if (field.type == BaseType.DateQuestion) { row.add( UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add( - UIInput( + getUiElement(objGiven, "inputFields[${index}].question", - label = "Hast du am ... Zeit?" + "Hast du am ... Zeit?" ) ) ) } - layout.add(row) } } @@ -292,6 +288,14 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return RestUtils.downloadFile(filename, bytes) } + //once created, questions should be ReadOnly + fun getUiElement(obj: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement{ + if (obj) + return UIReadOnlyField(id, label = label, dataType = dataType) + else + return UIInput(id, label = label, dataType = dataType) + } + // create a update layout funktion, welche das layout nummr updatet und zurück gibt es soll für jeden Frage Basistyp eine eigene funktion haben From f0e97339aeb2ffe7146309bcce7ca45182ee2970 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Thu, 20 Apr 2023 15:06:20 +0200 Subject: [PATCH 045/160] add group member as attendees --- .../projectforge/business/poll/PollDao.java | 0 .../org/projectforge/business/poll/PollDao.kt | 28 +++++++++--- .../rest/poll/Exel/ExcelExport.kt | 6 +-- .../projectforge/rest/poll/PollPageRest.kt | 44 +++++++++++++++---- 4 files changed, 61 insertions(+), 17 deletions(-) delete mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.java deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index 426a3e8cd1..adfb389d70 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -1,5 +1,6 @@ package org.projectforge.business.poll +import com.fasterxml.jackson.databind.ObjectMapper import org.projectforge.business.group.service.GroupService import org.projectforge.framework.access.OperationType import org.projectforge.framework.persistence.api.BaseDao @@ -54,14 +55,31 @@ open class PollDao : BaseDao(PollDO::class.java){ fun isAttendee(obj: PollDO): Boolean { val loggedInUser = user - if (!obj.attendeesIds.isNullOrBlank() && obj.attendeesIds!!.split(", ").contains(loggedInUser?.id.toString())) - return true - if (!obj.groupAttendeesIds.isNullOrBlank()){ - val groupIdArray = obj.groupAttendeesIds!!.split(", ").map { it.toInt() }.toIntArray() - val groupUsers = groupService?.getGroupUsers(groupIdArray) + + var listOfAttendeesIds = ObjectMapper().readValue(obj.attendeesIds, IntArray::class.java) + + if (loggedInUser != null) { + if(listOfAttendeesIds.contains(loggedInUser.id)){ + return true + } + } + + var stringBuilder = StringBuilder() + if (listOfAttendeesIds.isNotEmpty()){ + val groupUsers = groupService?.getGroupUsers(listOfAttendeesIds) + + groupUsers?.forEach{ + if (!listOfAttendeesIds.contains(it.id)){ + listOfAttendeesIds.plus(it.id) + } + } if(groupUsers?.contains(loggedInUser) == true) return true } + + obj.attendeesIds= ObjectMapper().writeValueAsString(listOfAttendeesIds) + + return false } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt index 70c235f66a..756c2d5da9 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt @@ -19,7 +19,7 @@ class ExcelExport { private val FIRST_DATA_ROW_NUM = 5 - fun getExcel(obj: Poll): ByteArray? { + fun getExcel(poll: Poll): ByteArray? { //var excelSheet: ExcelSheet? = null //var emptyRow: ExcelRow? = null @@ -30,8 +30,8 @@ class ExcelExport { ExcelWorkbook(classPathResource.inputStream, classPathResource.file.name).use { workbook -> val excelSheet = workbook.getSheet(0) val emptyRow = excelSheet.getRow(5) - val anzNewRows = 3 - //excelSheet.getRow(0).getCell(0).setCellValue(contentOfCell) + val anzNewRows = poll.attendees!!.size + // excelSheet.getRow(0).getCell(0).setCellValue(contentOfCell) createNewRow(excelSheet, emptyRow, anzNewRows) var hourCounter = 0.0 for (i in 0 until anzNewRows) { diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 7a0c0663e6..cd52f3d078 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -25,6 +25,8 @@ import org.springframework.web.bind.annotation.* import java.util.* import javax.servlet.http.HttpServletRequest + + @RestController @RequestMapping("${Rest.URL}/poll") class PollPageRest : AbstractDTOPagesRest(PollDao::class.java, "poll.title") { @@ -68,6 +70,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. Group.restoreDisplayNames(poll.fullAccessGroups, groupService) User.restoreDisplayNames(poll.attendees, userService) Group.restoreDisplayNames(poll.groupAttendees, groupService) + poll.attendees?.sortedBy { it.displayName } return poll } @@ -130,13 +133,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. addQuestionFieldset(layout, dto) - layout.watchFields.addAll( - arrayOf( - "title", "description", "location", "deadline", - "date" - ) - ) - + layout.watchFields.addAll(listOf("groupAttendees")) return LayoutUtils.processEditPage(layout, dto, this) } @@ -194,6 +191,37 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } + override fun onWatchFieldsUpdate( + request: HttpServletRequest, + dto: Poll, + watchFieldsTriggered: Array? + ): ResponseEntity { + + val userAccess = UILayout.UserAccess() + + val groupIds = dto.groupAttendees?.filter{it.id != null}?.map{it.id!!}?.toIntArray() + val userIds = UserService().getUserIds(groupService.getGroupUsers(groupIds)) + val users = User.toUserList(userIds) + User.restoreDisplayNames(users, userService) + val allUsers = users?.toMutableList()?: mutableListOf() + + dto.attendees?.forEach { user -> + if(allUsers?.filter { it.id == user.id }?.isEmpty() == true) { + allUsers.add(user) + } + } + + dto.attendees = allUsers.sortedBy { it.displayName } + + return ResponseEntity.ok( + ResponseAction(targetType = TargetType.UPDATE) + .addVariable("ui", createEditLayout(dto, userAccess)) + .addVariable("data", dto) + ) + + } + + @PostMapping("/addPremadeQuestions") private fun addPremadeQuestionsField( @RequestBody postData: PostData, @@ -211,8 +239,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) } - - private fun addQuestionFieldset(layout: UILayout, dto: Poll) { dto.inputFields?.forEachIndexed { index, field -> val row = UIRow() From d48660a2e0c3d8d2408a9a58efb311c394a2af6c Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Thu, 20 Apr 2023 15:10:42 +0200 Subject: [PATCH 046/160] merge dev into branch --- .../projectforge/menu/builder/MenuCreator.kt | 2 + .../menu/builder/MenuItemDefId.kt | 1 + .../projectforge/rest/poll/PollPageRest.kt | 199 +++++++++++------- .../rest/poll/types/PremadeQuestions.kt | 13 +- .../projectforge/rest/poll/types/Question.kt | 4 +- 5 files changed, 135 insertions(+), 84 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuCreator.kt b/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuCreator.kt index c7d60e11b3..c86ef61179 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuCreator.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuCreator.kt @@ -36,6 +36,7 @@ import org.projectforge.business.orga.ContractDao import org.projectforge.business.orga.PostausgangDao import org.projectforge.business.orga.PosteingangDao import org.projectforge.business.orga.VisitorbookDao +import org.projectforge.business.poll.PollDao import org.projectforge.business.sipgate.SipgateConfiguration import org.projectforge.business.user.ProjectForgeGroup import org.projectforge.business.user.UserRightValue @@ -449,6 +450,7 @@ open class MenuCreator { requiredUserRightId = ContractDao.USER_RIGHT_ID, requiredUserRightValues = READONLY_READWRITE ) ) + .add(MenuItemDef(MenuItemDefId.POLL)) .add( MenuItemDef( MenuItemDefId.VISITORBOOK, diff --git a/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuItemDefId.kt b/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuItemDefId.kt index 677f89bcb5..b105dbdc39 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuItemDefId.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuItemDefId.kt @@ -86,6 +86,7 @@ enum class MenuItemDefId constructor(val i18nKey: String, val url: String? = nul OUTGOING_INVOICE_LIST("menu.fibu.rechnungen", "wa/outgoingInvoiceList"), // PERSONAL_STATISTICS("menu.personalStatistics", "wa/personalStatistics"), // PHONE_CALL("menu.phoneCall", "wa/phoneCall"), // + POLL("menu.poll", getReactListUrl("poll")), // PROJECT_LIST("menu.fibu.projekte", getReactListUrl("project")), // REPORT_OBJECTIVES("menu.fibu.reporting.reportObjectives", "wa/reportObjectives"), // SEND_SMS("menu.sendSms", "wa/sendSms"), // diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 7495a795e7..fab19008f0 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -91,50 +91,79 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val poll = PollDO() dto.copyTo(poll) val layout = super.createEditLayout(dto, userAccess) - layout.add( - UIRow().add( - UIFieldset(UILength(md = 6, lg = 4)).add(lc, "title", "description", "location") - .add(lc, "owner") - .add(lc, "deadline", "date") - .add(UISelect.createUserSelect(lc, "fullAccessUsers", true, "poll.fullAccessUsers")) - .add(UISelect.createGroupSelect(lc, "fullAccessGroups", true, "poll.fullAccessGroups")) - .add(UISelect.createUserSelect(lc, "attendees", true, "poll.attendees")) - .add(UISelect.createGroupSelect(lc, "groupAttendees", true, "poll.groupAttendees")) - ) - ) - if(dto.id == null){ - layout.add( - UIRow().add( - UIFieldset(UILength(md = 6, lg = 4)).add( - UIButton.createDefaultButton( - id = "add-question-button", - responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST), - title = "Eigene Frage hinzufügen" + + val fieldset = UIFieldset(UILength(12)) + fieldset + .add(lc, "title", "description", "location") + .add(lc, "owner") + .add(lc, "deadline", "date") + .add(UISelect.createUserSelect(lc, "fullAccessUsers", true, "poll.fullAccessUsers")) + .add(UISelect.createGroupSelect(lc, "fullAccessGroups", true, "poll.fullAccessGroups")) + .add(UISelect.createUserSelect(lc, "attendees", true, "poll.attendees")) + .add(UISelect.createGroupSelect(lc, "groupAttendees", true, "poll.groupAttendees")) + if(dto.id == null) { + fieldset.add( + UIRow() + .add( + UICol(UILength(xs = 9, sm = 9, md = 9, lg = 9)) + .add( + UISelect( + "questionType", + values = BaseType.values().map { UISelectValue(it, it.name) }, + label = "questionType" + ) + ) ) - ).add( - UISelect("questionType", values = BaseType.values().map { UISelectValue(it, it.name) }) - ) - ) - ) - layout.add( - UIRow().add( - UIFieldset(UILength(md = 6, lg = 4)).add( - UIButton.createDefaultButton( - id = "micromata-vorlage-button", - responseAction = ResponseAction("${Rest.URL}/poll/addPremadeQuestions", targetType = TargetType.POST), - title = "Micromata Vorlage nutzen" + .add( + UICol(UILength(xs = 3, sm = 3, md = 3, lg = 3)) + .add( + UIButton.createDefaultButton( + id = "add-question-button", + responseAction = ResponseAction( + "${Rest.URL}/poll/add", + targetType = TargetType.POST + ), + title = "Eigene Frage hinzufügen" + ) + ) ) - ) ) - ) - } + .add( + UIRow() + .add( + UICol(UILength(xs = 9, sm = 9, md = 9, lg = 9)) + ) + .add( + UICol(UILength(xs = 3, sm = 3, md = 3, lg = 3)) + .add( + UIButton.createDefaultButton( + id = "micromata-vorlage-button", + responseAction = ResponseAction( + "${Rest.URL}/poll/addPremadeQuestions", + targetType = TargetType.POST + ), + title = "Micromata Vorlage nutzen" + ) + ) + ) + ) + } + + layout.add(fieldset) addQuestionFieldset(layout, dto) + layout.watchFields.addAll( + arrayOf( + "title", "description", "location", "deadline", + "date" + ) + ) + return LayoutUtils.processEditPage(layout, dto, this) } - //TODO refactor this whole file into multiple smaller files + override fun onAfterSaveOrUpdate(request: HttpServletRequest, poll: PollDO, postData: PostData) { super.onAfterSaveOrUpdate(request, poll, postData) @@ -143,8 +172,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } - @PostMapping("/addAntwort/{fieldId}") - fun addAntwortFeld( + @PostMapping("/addAnswer/{fieldId}") + fun addAnswerForMultipleChoice( @RequestBody postData: PostData, @PathVariable("fieldId") fieldUid: String, ): ResponseEntity { @@ -210,69 +239,97 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. private fun addQuestionFieldset(layout: UILayout, dto: Poll) { dto.inputFields?.forEachIndexed { index, field -> val objGiven = dto.id != null - val row = UIRow() + val fieldset = UIFieldset(UILength(12), title = field.type.toString()) + .add(generateDeleteButton(layout, field.uid)) + .add(getUiElement(objGiven, "inputFields[${index}].question", "Frage")) if (field.type == BaseType.YesNoQuestion) { - val groupLayout = UIGroup() + val buttons = UIGroup() field.answers?.forEach { answer -> - groupLayout.add( + buttons.add( UIRadioButton( "YesNoQuestion[${index}].question", answer, label = answer ) ) } - row.add( - UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add(getUiElement(objGiven, "inputFields[${index}].question")).add - (groupLayout) - ) - } - - if (field.type == BaseType.TextQuestion) { - row.add( - UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add(getUiElement(objGiven,"inputFields[${index}].question")) - ) + fieldset + .add(buttons) } - if (field.type == BaseType.MultipleChoices || field.type == BaseType.DropDownQuestion) { - val f = UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()) - .add(getUiElement(objGiven, "inputFields[${index}].question", "Die Frage")) + if (field.type == BaseType.MultipleChoices) { + val groupLayout = UIGroup() field.answers?.forEachIndexed { i, _ -> - f.add(getUiElement(objGiven, "inputFields[${index}].answers[${i}]", "Antwortmöglichkeit ${i + 1}")) + groupLayout.add(getUiElement(objGiven, "inputFields[${index}].answers[${i}]", "Antwortmöglichkeit ${i + 1}")) } if(!objGiven) { - f.add( + groupLayout.add( UIButton.createAddButton( responseAction = ResponseAction( - "${Rest.URL}/poll/addAntwort/${field.uid}", targetType = TargetType.POST + "${Rest.URL}/poll/addAnswer/${field.uid}", targetType = TargetType.POST ) ) ) } if (field.type == BaseType.MultipleChoices) { - f.add( + groupLayout.add( getUiElement(objGiven, - "inputFields[${index}].numberOfSelect", "Wie viele sollen angeklickt werden können?", - UIDataType.INT + "inputFields[${index}].numberOfSelect", "Wie viele sollen " + + "angeklickt werden können?", UIDataType.INT ) ) } - row.add(f) + fieldset.add(groupLayout) } if (field.type == BaseType.DateQuestion) { - row.add( + fieldset + .add( UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add( getUiElement(objGiven, "inputFields[${index}].question", - "Hast du am ... Zeit?" + "Hast du am ... Zeit?" ) ) - ) } - layout.add(row) + layout.add(fieldset) } } + private fun generateDeleteButton(layout: UILayout, uid:String?):UIRow { + val row = UIRow() + row.add( + UICol(UILength(11)) + ) + .add( + UICol(length = UILength(1)) + .add( + UIButton.createDangerButton( + id = "X", + responseAction = ResponseAction( + "${Rest.URL}/poll/deleteQuestion/${uid}", targetType = TargetType.POST + ) + ).withConfirmMessage(layout, confirmMessage = "Willst du wirklich diese Frage löschen?")) + ) + return row + } + + @PostMapping("/deleteQuestion/{uid}") + fun removeQuestion( + @RequestBody postData: PostData, + @PathVariable("uid") uid: String, + ): ResponseEntity { + val dto = postData.data + val userAccess = UILayout.UserAccess(insert = true, update = true) + val poll = PollDO() + + val matchingQuestion: Question? = dto.inputFields?.find { it.uid.equals(uid) } + dto.inputFields?.remove(matchingQuestion) + + dto.copyTo(poll) + return ResponseEntity.ok( + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ) + } @PostMapping("Export") fun export(request: HttpServletRequest,poll: Poll) : ResponseEntity? { @@ -295,18 +352,4 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. else return UIInput(id, label = label, dataType = dataType) } - - // create a update layout funktion, welche das layout nummr updatet und zurück gibt es soll für jeden Frage Basistyp eine eigene funktion haben - - - /*dto.inputFields?.forEachIndexed { field, index -> - if (field.type == msc) { - layout.add() // - "type[$index]" - Id: name - } - } - layout.add(UIRow().add(UIFieldset(UILength(md = 6, lg = 4)) - .add(lc, "name"))) - */ } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt index 7e5f69784c..623adf7360 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt @@ -1,34 +1,41 @@ package org.projectforge.rest.poll.types +import java.util.UUID + val PREMADE_QUESTIONS = mapOf( "HAS_FOOD" to Question( + uid = UUID.randomUUID().toString(), question = "Was willst du essen?", - type = BaseType.MultipleChoices, - answers = mutableListOf("Fleisch", "Vegetarisch", "Vegan"), - numberOfSelect = 1 + type = BaseType.YesNoQuestion, + answers = mutableListOf("Fleisch", "Vegetarisch", "Vegan") ), "IS_REMOTE" to Question( + uid = UUID.randomUUID().toString(), question = "Nimmst du remote teil?", type = BaseType.YesNoQuestion, answers = mutableListOf("Ja", "Nein") ), "CAN_HAVE_COMPANIONS" to Question( + uid = UUID.randomUUID().toString(), question = "Nimmst du eine Begleitung mit? (Name der Begleitung)", type = BaseType.TextQuestion, answers = mutableListOf("") ), "CAN_HAVE_CHILDREN" to Question( + uid = UUID.randomUUID().toString(), question = "Nimmst du ein Kind mit? (Name der Begleitung)", type = BaseType.TextQuestion, answers = mutableListOf("") ), "CAN_STAY_OVERNIGHT" to Question( + uid = UUID.randomUUID().toString(), question = "Willst du dort übernachten?", type = BaseType.YesNoQuestion, answers = mutableListOf("Ja", "Nein") ), "HAS_BREAKFAST" to Question( + uid = UUID.randomUUID().toString(), question = "Willst du am nächsten Tag frühstücken?", type = BaseType.YesNoQuestion, answers = mutableListOf("Ja", "Nein") diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt index 388f6bd272..9e12f88192 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt @@ -24,7 +24,5 @@ enum class BaseType { YesNoQuestion, DateQuestion, MultipleChoices, - TextQuestion, - DropDownQuestion, - PremadeQuestion + TextQuestion } \ No newline at end of file From e01787b50a1a1648aa90c26e3115654b1a7dc1bd Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Thu, 20 Apr 2023 15:27:52 +0200 Subject: [PATCH 047/160] Fix pr --- .../business/poll/PollResponseDO.kt | 1 - .../projectforge/rest/poll/PollPageRest.kt | 17 +++ .../projectforge/rest/poll/PollResponse.kt | 6 +- .../rest/poll/ResponsePageRest.kt | 104 ++++++++++-------- 4 files changed, 76 insertions(+), 52 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt index 449fa7f104..c79f36557c 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt @@ -1,7 +1,6 @@ package org.projectforge.business.poll import org.hibernate.search.annotations.Indexed -import org.projectforge.business.poll.PollDO import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.persistence.api.AUserRightId import org.projectforge.framework.persistence.entities.DefaultBaseDO diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index eb451ab24c..6f461db607 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -40,6 +40,18 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @Autowired private lateinit var pollMailService: PollMailService + /* @GetMapping("/edit/{id}") + fun getForm(@RequestBody postData: PostData,request: HttpServletRequest, @RequestParam("id") pollStringId: String?): ResponseEntity { + val dto = postData.data + val userAccess = UILayout.UserAccess(insert = true, update = true) + + return ResponseEntity.ok( + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", FormLayoutData(pollResponse, layout, createServerData(request))) + ) + } + + */ + override fun newBaseDTO(request: HttpServletRequest?): Poll { val result = Poll() result.owner = ThreadLocalUserContext.user @@ -94,6 +106,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val fieldset = UIFieldset(UILength(12)) fieldset + .add(UIButton.createDefaultButton( + id = "response-poll-button", + responseAction = ResponseAction(PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "${dto.id}", targetType = TargetType.REDIRECT), + title = "poll.response.poll" + )) .add(lc, "title", "description", "location") .add(lc, "owner") .add(lc, "deadline", "date") diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt index a8ff1d0945..d29cdfb1db 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt @@ -1,12 +1,10 @@ package org.projectforge.rest.poll import com.fasterxml.jackson.databind.ObjectMapper -import org.apache.poi.ss.formula.functions.T import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollResponseDO import org.projectforge.framework.persistence.user.entities.PFUserDO import org.projectforge.rest.dto.BaseDTO -import org.projectforge.rest.poll.types.Frage class PollResponse: BaseDTO() { var poll: PollDO? = null @@ -15,8 +13,6 @@ class PollResponse: BaseDTO() { override fun copyTo(dest: PollResponseDO) { if (!this.responses.isNullOrEmpty()) { - - dest.responses = ObjectMapper().writeValueAsString(this.responses) } super.copyTo(dest) @@ -34,7 +30,7 @@ class PollResponse: BaseDTO() { class Answer { var uid: String? = null var questionUid: String? = "" - var antworten: MutableList? = mutableListOf() + var answers: MutableList? = mutableListOf() fun toObject(string:String): Answer { return ObjectMapper().readValue(string, Answer::class.java) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 47ebb021de..7ef840c2c4 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -1,7 +1,6 @@ package org.projectforge.rest.poll import com.fasterxml.jackson.databind.ObjectMapper -import org.projectforge.Constants import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao import org.projectforge.business.poll.PollResponseDO @@ -22,9 +21,8 @@ import org.springframework.web.bind.annotation.* import java.util.UUID import javax.servlet.http.HttpServletRequest - @RestController -@RequestMapping("${Rest.URL}/antwort") +@RequestMapping("${Rest.URL}/response") class ResponsePageRest : AbstractDynamicPageRest() { @Autowired @@ -34,13 +32,12 @@ class ResponsePageRest : AbstractDynamicPageRest() { private lateinit var pollResponseDao: PollResponseDao @GetMapping("dynamic") - fun test(request: HttpServletRequest, @RequestParam("id") pollStringId: String?): FormLayoutData { - //val lc = LayoutContext(PollDO::class.java) + fun getForm(request: HttpServletRequest, @RequestParam("id") pollStringId: String?): FormLayoutData { val id = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") val pollData = pollDao.internalGetById(id) ?: PollDO() val pollDto = transformPollFromDB(pollData) - val layout = UILayout("poll.antwort.title") + val layout = UILayout("poll.response.title") val fieldSet = UIFieldset(12, title = pollDto.title) fieldSet .add(UIReadOnlyField(value = pollDto.description, label = "Description")) @@ -57,17 +54,16 @@ class ResponsePageRest : AbstractDynamicPageRest() { response.owner == ThreadLocalUserContext.user && response.poll?.id == pollData.id }?.let { - pollResponse.copyFrom(it) - } + pollResponse.copyFrom(it) + } - //addQuestions(layout, pollDto) pollDto.inputFields?.forEachIndexed { index, field -> val fieldSet2 = UIFieldset(title = field.question) val answer = Answer() answer.uid = UUID.randomUUID().toString() answer.questionUid = field.uid - pollResponse.responses?.firstOrNull() { - it.questionUid==field.uid + pollResponse.responses?.firstOrNull { + it.questionUid == field.uid }.let { if (it == null) pollResponse.responses?.add(answer) @@ -75,42 +71,49 @@ class ResponsePageRest : AbstractDynamicPageRest() { val col = UICol() - if (field.type == BaseType.FreiTextFrage) { - col.add(UITextArea("responses[$index].antworten[0]")) + if (field.type == BaseType.TextQuestion) { + col.add(UITextArea("responses[$index].answers[0]")) } - if (field.type == BaseType.JaNeinFrage) { - col.add(UIRadioButton("responses[$index].antworten[0]", value = field.antworten!![0], label = field.antworten?.get(0) ?: "")) - col.add(UIRadioButton("responses[$index].antworten[0]", value = field.antworten!![1], label = field.antworten?.get(1) ?: "")) + if (field.type == BaseType.YesNoQuestion) { + col.add( + UIRadioButton( + "responses[$index].answers[0]", + value = field.answers!![0], + label = field.answers?.get(0) ?: "" + ) + ) + col.add( + UIRadioButton( + "responses[$index].answers[0]", + value = field.answers!![1], + label = field.answers?.get(1) ?: "" + ) + ) } - if (field.type == BaseType.DatumsAbfrage) { - col.add(UITextArea("responses[$index].antworten[0]")) - } - if (field.type == BaseType.DropDownFrage) { - col.add(UISelect("responses[$index].antworten[0]", values = field.antworten?.map { UISelectValue(it, it) })) + if (field.type == BaseType.DateQuestion) { + col.add(UITextArea("responses[$index].answers[0]")) } if (field.type == BaseType.MultipleChoices) { - //for (i in 1 until field.antworten!!.size) - //answer.antworten?.add("") - - - field.antworten?.forEachIndexed { index2, _ -> - col.add(UICheckbox("responses[$index].antworten[$index2]", label = field.antworten?.get(index2) ?: "")) + field.answers?.forEachIndexed { index2, _ -> + col.add(UICheckbox("responses[$index].answers[$index2]", label = field.answers?.get(index2) ?: "")) } } fieldSet2.add(UIRow().add(col)) layout.add(fieldSet2) } - layout.add(UIButton.createDefaultButton( - id = "doResponse", - title = "response", - responseAction = ResponseAction( - RestResolver.getRestUrl( - this::class.java, - "doResponse" - ), targetType = TargetType.POST + layout.add( + UIButton.createDefaultButton( + id = "doResponse", + title = "response", + responseAction = ResponseAction( + RestResolver.getRestUrl( + this::class.java, + "doResponse" + ), targetType = TargetType.POST + ) ) - )) + ) return FormLayoutData(pollResponse, layout, createServerData(request)) } @@ -119,7 +122,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { fun doResponse( request: HttpServletRequest, @RequestBody postData: PostData - ): ResponseAction { + ): ResponseEntity? { val pollResponseDO = PollResponseDO() postData.data.copyTo(pollResponseDO) @@ -127,25 +130,34 @@ class ResponsePageRest : AbstractDynamicPageRest() { pollResponseDao.internalLoadAll().firstOrNull { pollResponse -> pollResponse.owner == ThreadLocalUserContext.user - && pollResponse.poll?.id == postData.data.poll?.id } - ?.let { - it.responses = pollResponseDO.responses - pollResponseDao.update(it) - return ResponseAction(targetType = TargetType.REDIRECT, url = "") - } + && pollResponse.poll?.id == postData.data.poll?.id + }?.let { + it.responses = pollResponseDO.responses + pollResponseDao.update(it) + return ResponseEntity.ok( + ResponseAction( + targetType = TargetType.REDIRECT, + url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + ) + ) + } pollResponseDao.saveOrUpdate(pollResponseDO) - return ResponseAction(targetType = TargetType.REDIRECT, url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true)) + return ResponseEntity.ok( + ResponseAction( + targetType = TargetType.REDIRECT, + url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + ) + ) } - private fun transformPollFromDB(obj: PollDO): Poll { val poll = Poll() poll.copyFrom(obj) if (obj.inputFields != null) { val a = ObjectMapper().readValue(obj.inputFields, MutableList::class.java) - poll.inputFields = a.map { Frage().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() + poll.inputFields = a.map { Question().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() } return poll } From 0335993e063ab14bc223709983ff4ba419d455ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Thu, 20 Apr 2023 15:53:44 +0200 Subject: [PATCH 048/160] Removed unused stuff --- .../org/projectforge/rest/poll/PollPageRest.kt | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index d4dedd8a30..4f2b0d7b1b 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -1,7 +1,6 @@ package org.projectforge.rest.poll import com.fasterxml.jackson.databind.ObjectMapper -import org.bouncycastle.asn1.x500.style.RFC4519Style.uid import org.projectforge.business.group.service.GroupService import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao @@ -89,8 +88,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { val lc = LayoutContext(PollDO::class.java) - val poll = PollDO() - dto.copyTo(poll) val layout = super.createEditLayout(dto, userAccess) val fieldset = UIFieldset(UILength(12)) @@ -154,7 +151,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun onAfterSaveOrUpdate(request: HttpServletRequest, poll: PollDO, postData: PostData) { super.onAfterSaveOrUpdate(request, poll, postData) - val dto = postData.data pollMailService.sendMail(subject = "", content = "", to = "test.mail") } @@ -184,7 +180,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ): ResponseEntity { val userAccess = UILayout.UserAccess(insert = true, update = true) val dto = postData.data - val poll = PollDO() var type = BaseType.valueOf(dto.questionType ?: "TextQuestion") var question = Question(uid = UUID.randomUUID().toString(), type = type) @@ -197,7 +192,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.inputFields!!.add(question) - dto.copyTo(poll) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) @@ -210,13 +204,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ): ResponseEntity { val userAccess = UILayout.UserAccess(insert = true, update = true) val dto = postData.data - val poll = PollDO() PREMADE_QUESTIONS.entries.forEach { entry -> dto.inputFields?.add(entry.value) } - dto.copyTo(poll) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) @@ -290,12 +282,9 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ): ResponseEntity { val dto = postData.data val userAccess = UILayout.UserAccess(insert = true, update = true) - val poll = PollDO() dto.inputFields?.find { it.uid.equals(questionUid) }?.answers?.removeAt(answerIndex) - - dto.copyTo(poll) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) @@ -328,12 +317,10 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ): ResponseEntity { val dto = postData.data val userAccess = UILayout.UserAccess(insert = true, update = true) - val poll = PollDO() val matchingQuestion: Question? = dto.inputFields?.find { it.uid.equals(uid) } dto.inputFields?.remove(matchingQuestion) - dto.copyTo(poll) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) From c2a0749c88c8d11593dc085510094442ae775e76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Thu, 20 Apr 2023 16:43:34 +0200 Subject: [PATCH 049/160] Revert "Merge pull request #10 from NikitaMic/feature/singleAndMultipleResponseQuestions" This reverts commit 07c8e219ddb6d939a69b92230b4236bca91a8c52, reversing changes made to 84a090f8d8bf3404ecac7152c6ec7fbd3259f74f. --- .../projectforge/rest/poll/PollPageRest.kt | 87 ++++++++----------- .../rest/poll/types/PremadeQuestions.kt | 8 +- .../projectforge/rest/poll/types/Question.kt | 8 +- 3 files changed, 45 insertions(+), 58 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index e007640197..6f461db607 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -100,6 +100,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { val lc = LayoutContext(PollDO::class.java) + val poll = PollDO() + dto.copyTo(poll) val layout = super.createEditLayout(dto, userAccess) val fieldset = UIFieldset(UILength(12)) @@ -168,6 +170,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun onAfterSaveOrUpdate(request: HttpServletRequest, poll: PollDO, postData: PostData) { super.onAfterSaveOrUpdate(request, poll, postData) + val dto = postData.data pollMailService.sendMail(subject = "", content = "", to = "test.mail") } @@ -197,10 +200,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ): ResponseEntity { val userAccess = UILayout.UserAccess(insert = true, update = true) val dto = postData.data + val poll = PollDO() var type = BaseType.valueOf(dto.questionType ?: "TextQuestion") var question = Question(uid = UUID.randomUUID().toString(), type = type) - if(type == BaseType.SingleResponseQuestion) { + if(type == BaseType.YesNoQuestion) { question.answers = mutableListOf("ja", "nein") } if(type == BaseType.DateQuestion) { @@ -209,6 +213,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.inputFields!!.add(question) + dto.copyTo(poll) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) @@ -221,11 +226,13 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ): ResponseEntity { val userAccess = UILayout.UserAccess(insert = true, update = true) val dto = postData.data + val poll = PollDO() PREMADE_QUESTIONS.entries.forEach { entry -> dto.inputFields?.add(entry.value) } + dto.copyTo(poll) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) @@ -237,21 +244,39 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val fieldset = UIFieldset(UILength(12), title = field.type.toString()) .add(generateDeleteButton(layout, field.uid)) .add(UIInput("inputFields[${index}].question", label = "Frage")) + if (field.type == BaseType.YesNoQuestion) { + val buttons = UIGroup() + field.answers?.forEach { answer -> + buttons.add( + UIRadioButton( + "YesNoQuestion[${index}].question", answer, label = answer + ) + ) + } + fieldset + .add(buttons) + } - if (field.type == BaseType.SingleResponseQuestion || field.type == BaseType.MultiResponseQuestion) { + if (field.type == BaseType.MultipleChoices) { val groupLayout = UIGroup() - field.answers?.forEachIndexed { answerIndex, _ -> - groupLayout.add(generateSingleAndMultiResponseAnswer(index, field.uid, answerIndex, layout)) + field.answers?.forEachIndexed { i, _ -> + groupLayout.add(UIInput("inputFields[${index}].answers[${i}]", label = "Antwortmöglichkeit ${i + 1}")) } groupLayout.add( - UIRow().add( - UIButton.createAddButton( - responseAction = ResponseAction( - "${Rest.URL}/poll/addAnswer/${field.uid}", targetType = TargetType.POST - ) + UIButton.createAddButton( + responseAction = ResponseAction( + "${Rest.URL}/poll/addAnswer/${field.uid}", targetType = TargetType.POST ) ) ) + if (field.type == BaseType.MultipleChoices) { + groupLayout.add( + UIInput( + "inputFields[${index}].numberOfSelect", dataType = UIDataType.INT, label = "Wie viele sollen " + + "angeklickt werden können?" + ) + ) + } fieldset.add(groupLayout) } @@ -269,45 +294,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } } - private fun generateSingleAndMultiResponseAnswer(inputFieldIndex: Int, questionUid: String?, answerIndex: Int, layout: UILayout):UIRow { - val row = UIRow() - row.add( - UICol() - .add( - UIInput("inputFields[${inputFieldIndex}].answers[${answerIndex}]", label = "Answer ${answerIndex + 1}") - ) - ) - .add( - UICol() - .add( - UIButton.createDangerButton( - id = "X", - responseAction = ResponseAction( - "${Rest.URL}/poll/deleteAnswer/${questionUid}/${answerIndex}", targetType = TargetType.POST - ) - ).withConfirmMessage(layout, confirmMessage = "Willst du wirklich diese Antwort löschen?")) - ) - return row - } - - - @PostMapping("/deleteAnswer/{questionUid}/{answerIndex}") - fun deleteAnswerOfSingleAndMultipleResponseQuestion( - @RequestBody postData: PostData, - @PathVariable("questionUid") questionUid: String, - @PathVariable("answerIndex") answerIndex: Int - ): ResponseEntity { - val dto = postData.data - val userAccess = UILayout.UserAccess(insert = true, update = true) - - dto.inputFields?.find { it.uid.equals(questionUid) }?.answers?.removeAt(answerIndex) - - return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) - ) - } - - private fun generateDeleteButton(layout: UILayout, uid:String?):UIRow { val row = UIRow() row.add( @@ -326,18 +312,19 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return row } - @PostMapping("/deleteQuestion/{uid}") - fun deleteQuestion( + fun removeQuestion( @RequestBody postData: PostData, @PathVariable("uid") uid: String, ): ResponseEntity { val dto = postData.data val userAccess = UILayout.UserAccess(insert = true, update = true) + val poll = PollDO() val matchingQuestion: Question? = dto.inputFields?.find { it.uid.equals(uid) } dto.inputFields?.remove(matchingQuestion) + dto.copyTo(poll) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt index d1e47339cc..623adf7360 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt @@ -7,13 +7,13 @@ val PREMADE_QUESTIONS = mapOf( "HAS_FOOD" to Question( uid = UUID.randomUUID().toString(), question = "Was willst du essen?", - type = BaseType.SingleResponseQuestion, + type = BaseType.YesNoQuestion, answers = mutableListOf("Fleisch", "Vegetarisch", "Vegan") ), "IS_REMOTE" to Question( uid = UUID.randomUUID().toString(), question = "Nimmst du remote teil?", - type = BaseType.SingleResponseQuestion, + type = BaseType.YesNoQuestion, answers = mutableListOf("Ja", "Nein") ), "CAN_HAVE_COMPANIONS" to Question( @@ -31,13 +31,13 @@ val PREMADE_QUESTIONS = mapOf( "CAN_STAY_OVERNIGHT" to Question( uid = UUID.randomUUID().toString(), question = "Willst du dort übernachten?", - type = BaseType.SingleResponseQuestion, + type = BaseType.YesNoQuestion, answers = mutableListOf("Ja", "Nein") ), "HAS_BREAKFAST" to Question( uid = UUID.randomUUID().toString(), question = "Willst du am nächsten Tag frühstücken?", - type = BaseType.SingleResponseQuestion, + type = BaseType.YesNoQuestion, answers = mutableListOf("Ja", "Nein") ), ) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt index 085fc28a86..9e12f88192 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt @@ -21,8 +21,8 @@ class Question( } enum class BaseType { - TextQuestion, - SingleResponseQuestion, - MultiResponseQuestion, - DateQuestion + YesNoQuestion, + DateQuestion, + MultipleChoices, + TextQuestion } \ No newline at end of file From 76042ed221dcb71a5fe9b67da6c053c38841ed5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Fri, 21 Apr 2023 09:25:54 +0200 Subject: [PATCH 050/160] Copied the changes from feature/singleAndMutlipleResponseQuestion branch --- .../business/poll/PollResponseDO.kt | 30 ---- .../business/poll/PollResponseDao.kt | 23 --- .../migrate/common/V7.5.1.2__7.5.1.0-POLL.sql | 20 +-- .../projectforge/rest/poll/AntwortSeite.kt | 35 ++++ .../projectforge/rest/poll/PollPageRest.kt | 104 ++++++----- .../projectforge/rest/poll/PollResponse.kt | 38 ---- .../rest/poll/ResponsePageRest.kt | 164 ------------------ .../rest/poll/types/PremadeQuestions.kt | 8 +- .../projectforge/rest/poll/types/Question.kt | 8 +- 9 files changed, 94 insertions(+), 336 deletions(-) delete mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt delete mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt delete mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt delete mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt deleted file mode 100644 index c79f36557c..0000000000 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.projectforge.business.poll - -import org.hibernate.search.annotations.Indexed -import org.projectforge.common.anots.PropertyInfo -import org.projectforge.framework.persistence.api.AUserRightId -import org.projectforge.framework.persistence.entities.DefaultBaseDO -import org.projectforge.framework.persistence.user.entities.PFUserDO -import org.springframework.context.annotation.DependsOn -import javax.persistence.* - -@Entity -@Indexed -@Table(name = "t_poll_response") -@AUserRightId(value = "poll.response", checkAccess = false) -@DependsOn("org.projectforge.framework.persistence.user.entities.PFUserDO") -open class PollResponseDO : DefaultBaseDO() { - @get:PropertyInfo(i18nKey = "poll.response.poll") - @get:ManyToOne(fetch = FetchType.LAZY) - @get:JoinColumn(name = "poll_pk", nullable = false) - open var poll: PollDO? = null - - @get:PropertyInfo(i18nKey = "poll.response.owner") - @get:ManyToOne(fetch = FetchType.LAZY) - @get:JoinColumn(name = "owner_pk", nullable = false) - open var owner: PFUserDO? = null - - @PropertyInfo(i18nKey = "poll.responses") - @get:Column(name = "responses", nullable = true, length = 1000) - open var responses: String? = null -} \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt deleted file mode 100644 index 66cd987917..0000000000 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.projectforge.business.poll - -import org.projectforge.framework.access.OperationType -import org.projectforge.framework.persistence.api.BaseDao -import org.projectforge.framework.persistence.user.entities.PFUserDO -import org.springframework.stereotype.Repository - -@Repository -open class PollResponseDao : BaseDao(PollResponseDO::class.java) { - override fun newInstance(): PollResponseDO { - return PollResponseDO() - } - - override fun hasAccess( - user: PFUserDO?, - obj: PollResponseDO?, - oldObj: PollResponseDO?, - operationType: OperationType?, - throwException: Boolean - ): Boolean { - return true - } -} \ No newline at end of file diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql index 3d6b1fd54d..0f2c9a8fa5 100644 --- a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql @@ -23,22 +23,4 @@ CREATE TABLE T_POLL ALTER TABLE T_POLL ADD CONSTRAINT t_poll_pkey PRIMARY KEY (pk); ALTER TABLE T_POLL - ADD CONSTRAINT fk_t_poll_pf_user FOREIGN KEY (owner_pk) REFERENCES t_pf_user (pk); - -CREATE TABLE T_POLL_RESPONSE -( - pk INTEGER NOT NULL, - deleted BOOLEAN NOT NULL, - created TIMESTAMP WITHOUT TIME ZONE, - last_update TIMESTAMP WITHOUT TIME ZONE, - poll_pk INTEGER NOT NULL, - owner_pk INTEGER NOT NULL, - responses CHARACTER Varying(10000) -); - -ALTER TABLE T_POLL_RESPONSE - ADD CONSTRAINT t_poll_response_pkey PRIMARY KEY (pk); -ALTER TABLE T_POLL_RESPONSE - ADD CONSTRAINT fk_t_poll_response_pf_user FOREIGN KEY (owner_pk) REFERENCES t_pf_user (pk); -ALTER TABLE T_POLL_RESPONSE - ADD CONSTRAINT fk_t_poll_response_poll FOREIGN KEY (poll_pk) REFERENCES t_poll (pk); \ No newline at end of file + ADD CONSTRAINT fk_t_poll_pf_user FOREIGN KEY (owner_pk) REFERENCES t_pf_user (pk); \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt new file mode 100644 index 0000000000..74d0c15ce1 --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt @@ -0,0 +1,35 @@ +package org.projectforge.rest.poll + +import org.projectforge.business.poll.PollDO +import org.projectforge.business.poll.PollDao +import org.projectforge.framework.persistence.api.MagicFilter +import org.projectforge.rest.config.Rest +import org.projectforge.rest.core.AbstractDTOPagesRest +import org.projectforge.ui.UILayout +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import javax.servlet.http.HttpServletRequest + + +@RestController +@RequestMapping("${Rest.URL}/poll/antwort") +class AntwortSeite : AbstractDTOPagesRest(PollDao::class.java, "poll.title") { + override fun createListLayout( + request: HttpServletRequest, + layout: UILayout, + magicFilter: MagicFilter, + userAccess: UILayout.UserAccess + ) { + TODO("Not yet implemented") + } + + override fun transformFromDB(obj: PollDO, editMode: Boolean): Poll { + TODO("Not yet implemented") + } + + override fun transformForDB(dto: Poll): PollDO { + TODO("Not yet implemented") + } + + +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 6f461db607..4f2b0d7b1b 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -40,18 +40,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @Autowired private lateinit var pollMailService: PollMailService - /* @GetMapping("/edit/{id}") - fun getForm(@RequestBody postData: PostData,request: HttpServletRequest, @RequestParam("id") pollStringId: String?): ResponseEntity { - val dto = postData.data - val userAccess = UILayout.UserAccess(insert = true, update = true) - - return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", FormLayoutData(pollResponse, layout, createServerData(request))) - ) - } - - */ - override fun newBaseDTO(request: HttpServletRequest?): Poll { val result = Poll() result.owner = ThreadLocalUserContext.user @@ -100,17 +88,10 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { val lc = LayoutContext(PollDO::class.java) - val poll = PollDO() - dto.copyTo(poll) val layout = super.createEditLayout(dto, userAccess) val fieldset = UIFieldset(UILength(12)) fieldset - .add(UIButton.createDefaultButton( - id = "response-poll-button", - responseAction = ResponseAction(PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "${dto.id}", targetType = TargetType.REDIRECT), - title = "poll.response.poll" - )) .add(lc, "title", "description", "location") .add(lc, "owner") .add(lc, "deadline", "date") @@ -170,7 +151,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun onAfterSaveOrUpdate(request: HttpServletRequest, poll: PollDO, postData: PostData) { super.onAfterSaveOrUpdate(request, poll, postData) - val dto = postData.data pollMailService.sendMail(subject = "", content = "", to = "test.mail") } @@ -200,11 +180,10 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ): ResponseEntity { val userAccess = UILayout.UserAccess(insert = true, update = true) val dto = postData.data - val poll = PollDO() var type = BaseType.valueOf(dto.questionType ?: "TextQuestion") var question = Question(uid = UUID.randomUUID().toString(), type = type) - if(type == BaseType.YesNoQuestion) { + if(type == BaseType.SingleResponseQuestion) { question.answers = mutableListOf("ja", "nein") } if(type == BaseType.DateQuestion) { @@ -213,7 +192,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.inputFields!!.add(question) - dto.copyTo(poll) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) @@ -226,13 +204,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ): ResponseEntity { val userAccess = UILayout.UserAccess(insert = true, update = true) val dto = postData.data - val poll = PollDO() PREMADE_QUESTIONS.entries.forEach { entry -> dto.inputFields?.add(entry.value) } - dto.copyTo(poll) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) @@ -244,39 +220,21 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val fieldset = UIFieldset(UILength(12), title = field.type.toString()) .add(generateDeleteButton(layout, field.uid)) .add(UIInput("inputFields[${index}].question", label = "Frage")) - if (field.type == BaseType.YesNoQuestion) { - val buttons = UIGroup() - field.answers?.forEach { answer -> - buttons.add( - UIRadioButton( - "YesNoQuestion[${index}].question", answer, label = answer - ) - ) - } - fieldset - .add(buttons) - } - if (field.type == BaseType.MultipleChoices) { + if (field.type == BaseType.SingleResponseQuestion || field.type == BaseType.MultiResponseQuestion) { val groupLayout = UIGroup() - field.answers?.forEachIndexed { i, _ -> - groupLayout.add(UIInput("inputFields[${index}].answers[${i}]", label = "Antwortmöglichkeit ${i + 1}")) + field.answers?.forEachIndexed { answerIndex, _ -> + groupLayout.add(generateSingleAndMultiResponseAnswer(index, field.uid, answerIndex, layout)) } groupLayout.add( - UIButton.createAddButton( - responseAction = ResponseAction( - "${Rest.URL}/poll/addAnswer/${field.uid}", targetType = TargetType.POST + UIRow().add( + UIButton.createAddButton( + responseAction = ResponseAction( + "${Rest.URL}/poll/addAnswer/${field.uid}", targetType = TargetType.POST + ) ) ) ) - if (field.type == BaseType.MultipleChoices) { - groupLayout.add( - UIInput( - "inputFields[${index}].numberOfSelect", dataType = UIDataType.INT, label = "Wie viele sollen " + - "angeklickt werden können?" - ) - ) - } fieldset.add(groupLayout) } @@ -294,6 +252,45 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } } + private fun generateSingleAndMultiResponseAnswer(inputFieldIndex: Int, questionUid: String?, answerIndex: Int, layout: UILayout):UIRow { + val row = UIRow() + row.add( + UICol() + .add( + UIInput("inputFields[${inputFieldIndex}].answers[${answerIndex}]", label = "Answer ${answerIndex + 1}") + ) + ) + .add( + UICol() + .add( + UIButton.createDangerButton( + id = "X", + responseAction = ResponseAction( + "${Rest.URL}/poll/deleteAnswer/${questionUid}/${answerIndex}", targetType = TargetType.POST + ) + ).withConfirmMessage(layout, confirmMessage = "Willst du wirklich diese Antwort löschen?")) + ) + return row + } + + + @PostMapping("/deleteAnswer/{questionUid}/{answerIndex}") + fun deleteAnswerOfSingleAndMultipleResponseQuestion( + @RequestBody postData: PostData, + @PathVariable("questionUid") questionUid: String, + @PathVariable("answerIndex") answerIndex: Int + ): ResponseEntity { + val dto = postData.data + val userAccess = UILayout.UserAccess(insert = true, update = true) + + dto.inputFields?.find { it.uid.equals(questionUid) }?.answers?.removeAt(answerIndex) + + return ResponseEntity.ok( + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ) + } + + private fun generateDeleteButton(layout: UILayout, uid:String?):UIRow { val row = UIRow() row.add( @@ -312,19 +309,18 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return row } + @PostMapping("/deleteQuestion/{uid}") - fun removeQuestion( + fun deleteQuestion( @RequestBody postData: PostData, @PathVariable("uid") uid: String, ): ResponseEntity { val dto = postData.data val userAccess = UILayout.UserAccess(insert = true, update = true) - val poll = PollDO() val matchingQuestion: Question? = dto.inputFields?.find { it.uid.equals(uid) } dto.inputFields?.remove(matchingQuestion) - dto.copyTo(poll) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt deleted file mode 100644 index d29cdfb1db..0000000000 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt +++ /dev/null @@ -1,38 +0,0 @@ -package org.projectforge.rest.poll - -import com.fasterxml.jackson.databind.ObjectMapper -import org.projectforge.business.poll.PollDO -import org.projectforge.business.poll.PollResponseDO -import org.projectforge.framework.persistence.user.entities.PFUserDO -import org.projectforge.rest.dto.BaseDTO - -class PollResponse: BaseDTO() { - var poll: PollDO? = null - var owner: PFUserDO? = null - var responses: MutableList? = mutableListOf() - - override fun copyTo(dest: PollResponseDO) { - if (!this.responses.isNullOrEmpty()) { - dest.responses = ObjectMapper().writeValueAsString(this.responses) - } - super.copyTo(dest) - } - - override fun copyFrom(src: PollResponseDO) { - if (!src.responses.isNullOrEmpty()) { - val a = ObjectMapper().readValue(src.responses, MutableList::class.java) - this.responses = a.map { Answer().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() - } - super.copyFrom(src) - } -} - -class Answer { - var uid: String? = null - var questionUid: String? = "" - var answers: MutableList? = mutableListOf() - - fun toObject(string:String): Answer { - return ObjectMapper().readValue(string, Answer::class.java) - } -} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt deleted file mode 100644 index 7ef840c2c4..0000000000 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ /dev/null @@ -1,164 +0,0 @@ -package org.projectforge.rest.poll - -import com.fasterxml.jackson.databind.ObjectMapper -import org.projectforge.business.poll.PollDO -import org.projectforge.business.poll.PollDao -import org.projectforge.business.poll.PollResponseDO -import org.projectforge.business.poll.PollResponseDao -import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext -import org.projectforge.framework.utils.NumberHelper -import org.projectforge.rest.config.Rest -import org.projectforge.rest.core.AbstractDynamicPageRest -import org.projectforge.rest.core.PagesResolver -import org.projectforge.rest.core.RestResolver -import org.projectforge.rest.dto.FormLayoutData -import org.projectforge.rest.dto.PostData -import org.projectforge.rest.poll.types.* -import org.projectforge.ui.* -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* -import java.util.UUID -import javax.servlet.http.HttpServletRequest - -@RestController -@RequestMapping("${Rest.URL}/response") -class ResponsePageRest : AbstractDynamicPageRest() { - - @Autowired - private lateinit var pollDao: PollDao - - @Autowired - private lateinit var pollResponseDao: PollResponseDao - - @GetMapping("dynamic") - fun getForm(request: HttpServletRequest, @RequestParam("id") pollStringId: String?): FormLayoutData { - val id = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") - val pollData = pollDao.internalGetById(id) ?: PollDO() - val pollDto = transformPollFromDB(pollData) - - val layout = UILayout("poll.response.title") - val fieldSet = UIFieldset(12, title = pollDto.title) - fieldSet - .add(UIReadOnlyField(value = pollDto.description, label = "Description")) - .add(UIReadOnlyField(value = pollDto.location, label = "Location")) - .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = "Owner")) - .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = "Deadline")) - - layout.add(fieldSet) - - val pollResponse = PollResponse() - pollResponse.poll = pollData - - pollResponseDao.internalLoadAll().firstOrNull { response -> - response.owner == ThreadLocalUserContext.user - && response.poll?.id == pollData.id - }?.let { - pollResponse.copyFrom(it) - } - - pollDto.inputFields?.forEachIndexed { index, field -> - val fieldSet2 = UIFieldset(title = field.question) - val answer = Answer() - answer.uid = UUID.randomUUID().toString() - answer.questionUid = field.uid - pollResponse.responses?.firstOrNull { - it.questionUid == field.uid - }.let { - if (it == null) - pollResponse.responses?.add(answer) - } - - val col = UICol() - - if (field.type == BaseType.TextQuestion) { - col.add(UITextArea("responses[$index].answers[0]")) - } - if (field.type == BaseType.YesNoQuestion) { - col.add( - UIRadioButton( - "responses[$index].answers[0]", - value = field.answers!![0], - label = field.answers?.get(0) ?: "" - ) - ) - col.add( - UIRadioButton( - "responses[$index].answers[0]", - value = field.answers!![1], - label = field.answers?.get(1) ?: "" - ) - ) - } - if (field.type == BaseType.DateQuestion) { - col.add(UITextArea("responses[$index].answers[0]")) - } - if (field.type == BaseType.MultipleChoices) { - field.answers?.forEachIndexed { index2, _ -> - col.add(UICheckbox("responses[$index].answers[$index2]", label = field.answers?.get(index2) ?: "")) - } - } - fieldSet2.add(UIRow().add(col)) - layout.add(fieldSet2) - } - - layout.add( - UIButton.createDefaultButton( - id = "doResponse", - title = "response", - responseAction = ResponseAction( - RestResolver.getRestUrl( - this::class.java, - "doResponse" - ), targetType = TargetType.POST - ) - ) - ) - - return FormLayoutData(pollResponse, layout, createServerData(request)) - } - - @PostMapping("doResponse") - fun doResponse( - request: HttpServletRequest, - @RequestBody postData: PostData - ): ResponseEntity? { - - val pollResponseDO = PollResponseDO() - postData.data.copyTo(pollResponseDO) - pollResponseDO.owner = ThreadLocalUserContext.user - - pollResponseDao.internalLoadAll().firstOrNull { pollResponse -> - pollResponse.owner == ThreadLocalUserContext.user - && pollResponse.poll?.id == postData.data.poll?.id - }?.let { - it.responses = pollResponseDO.responses - pollResponseDao.update(it) - return ResponseEntity.ok( - ResponseAction( - targetType = TargetType.REDIRECT, - url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) - ) - ) - } - - pollResponseDao.saveOrUpdate(pollResponseDO) - return ResponseEntity.ok( - ResponseAction( - targetType = TargetType.REDIRECT, - url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) - ) - ) - } - - - private fun transformPollFromDB(obj: PollDO): Poll { - val poll = Poll() - poll.copyFrom(obj) - if (obj.inputFields != null) { - val a = ObjectMapper().readValue(obj.inputFields, MutableList::class.java) - poll.inputFields = a.map { Question().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() - } - return poll - } -} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt index 623adf7360..d1e47339cc 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt @@ -7,13 +7,13 @@ val PREMADE_QUESTIONS = mapOf( "HAS_FOOD" to Question( uid = UUID.randomUUID().toString(), question = "Was willst du essen?", - type = BaseType.YesNoQuestion, + type = BaseType.SingleResponseQuestion, answers = mutableListOf("Fleisch", "Vegetarisch", "Vegan") ), "IS_REMOTE" to Question( uid = UUID.randomUUID().toString(), question = "Nimmst du remote teil?", - type = BaseType.YesNoQuestion, + type = BaseType.SingleResponseQuestion, answers = mutableListOf("Ja", "Nein") ), "CAN_HAVE_COMPANIONS" to Question( @@ -31,13 +31,13 @@ val PREMADE_QUESTIONS = mapOf( "CAN_STAY_OVERNIGHT" to Question( uid = UUID.randomUUID().toString(), question = "Willst du dort übernachten?", - type = BaseType.YesNoQuestion, + type = BaseType.SingleResponseQuestion, answers = mutableListOf("Ja", "Nein") ), "HAS_BREAKFAST" to Question( uid = UUID.randomUUID().toString(), question = "Willst du am nächsten Tag frühstücken?", - type = BaseType.YesNoQuestion, + type = BaseType.SingleResponseQuestion, answers = mutableListOf("Ja", "Nein") ), ) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt index 9e12f88192..085fc28a86 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt @@ -21,8 +21,8 @@ class Question( } enum class BaseType { - YesNoQuestion, - DateQuestion, - MultipleChoices, - TextQuestion + TextQuestion, + SingleResponseQuestion, + MultiResponseQuestion, + DateQuestion } \ No newline at end of file From 1400004c0cf4dc56218e3a19e947002d6044747f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Fri, 21 Apr 2023 09:51:36 +0200 Subject: [PATCH 051/160] Restored feature/antworten --- .../business/poll/PollResponseDO.kt | 30 ++++ .../business/poll/PollResponseDao.kt | 23 +++ .../migrate/common/V7.5.1.2__7.5.1.0-POLL.sql | 20 ++- .../projectforge/rest/poll/AntwortSeite.kt | 35 ---- .../projectforge/rest/poll/PollPageRest.kt | 5 + .../projectforge/rest/poll/PollResponse.kt | 38 ++++ .../rest/poll/ResponsePageRest.kt | 164 ++++++++++++++++++ 7 files changed, 279 insertions(+), 36 deletions(-) create mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt create mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt delete mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt new file mode 100644 index 0000000000..c79f36557c --- /dev/null +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt @@ -0,0 +1,30 @@ +package org.projectforge.business.poll + +import org.hibernate.search.annotations.Indexed +import org.projectforge.common.anots.PropertyInfo +import org.projectforge.framework.persistence.api.AUserRightId +import org.projectforge.framework.persistence.entities.DefaultBaseDO +import org.projectforge.framework.persistence.user.entities.PFUserDO +import org.springframework.context.annotation.DependsOn +import javax.persistence.* + +@Entity +@Indexed +@Table(name = "t_poll_response") +@AUserRightId(value = "poll.response", checkAccess = false) +@DependsOn("org.projectforge.framework.persistence.user.entities.PFUserDO") +open class PollResponseDO : DefaultBaseDO() { + @get:PropertyInfo(i18nKey = "poll.response.poll") + @get:ManyToOne(fetch = FetchType.LAZY) + @get:JoinColumn(name = "poll_pk", nullable = false) + open var poll: PollDO? = null + + @get:PropertyInfo(i18nKey = "poll.response.owner") + @get:ManyToOne(fetch = FetchType.LAZY) + @get:JoinColumn(name = "owner_pk", nullable = false) + open var owner: PFUserDO? = null + + @PropertyInfo(i18nKey = "poll.responses") + @get:Column(name = "responses", nullable = true, length = 1000) + open var responses: String? = null +} \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt new file mode 100644 index 0000000000..66cd987917 --- /dev/null +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt @@ -0,0 +1,23 @@ +package org.projectforge.business.poll + +import org.projectforge.framework.access.OperationType +import org.projectforge.framework.persistence.api.BaseDao +import org.projectforge.framework.persistence.user.entities.PFUserDO +import org.springframework.stereotype.Repository + +@Repository +open class PollResponseDao : BaseDao(PollResponseDO::class.java) { + override fun newInstance(): PollResponseDO { + return PollResponseDO() + } + + override fun hasAccess( + user: PFUserDO?, + obj: PollResponseDO?, + oldObj: PollResponseDO?, + operationType: OperationType?, + throwException: Boolean + ): Boolean { + return true + } +} \ No newline at end of file diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql index 0f2c9a8fa5..3d6b1fd54d 100644 --- a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql @@ -23,4 +23,22 @@ CREATE TABLE T_POLL ALTER TABLE T_POLL ADD CONSTRAINT t_poll_pkey PRIMARY KEY (pk); ALTER TABLE T_POLL - ADD CONSTRAINT fk_t_poll_pf_user FOREIGN KEY (owner_pk) REFERENCES t_pf_user (pk); \ No newline at end of file + ADD CONSTRAINT fk_t_poll_pf_user FOREIGN KEY (owner_pk) REFERENCES t_pf_user (pk); + +CREATE TABLE T_POLL_RESPONSE +( + pk INTEGER NOT NULL, + deleted BOOLEAN NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE, + last_update TIMESTAMP WITHOUT TIME ZONE, + poll_pk INTEGER NOT NULL, + owner_pk INTEGER NOT NULL, + responses CHARACTER Varying(10000) +); + +ALTER TABLE T_POLL_RESPONSE + ADD CONSTRAINT t_poll_response_pkey PRIMARY KEY (pk); +ALTER TABLE T_POLL_RESPONSE + ADD CONSTRAINT fk_t_poll_response_pf_user FOREIGN KEY (owner_pk) REFERENCES t_pf_user (pk); +ALTER TABLE T_POLL_RESPONSE + ADD CONSTRAINT fk_t_poll_response_poll FOREIGN KEY (poll_pk) REFERENCES t_poll (pk); \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt deleted file mode 100644 index 74d0c15ce1..0000000000 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.projectforge.rest.poll - -import org.projectforge.business.poll.PollDO -import org.projectforge.business.poll.PollDao -import org.projectforge.framework.persistence.api.MagicFilter -import org.projectforge.rest.config.Rest -import org.projectforge.rest.core.AbstractDTOPagesRest -import org.projectforge.ui.UILayout -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController -import javax.servlet.http.HttpServletRequest - - -@RestController -@RequestMapping("${Rest.URL}/poll/antwort") -class AntwortSeite : AbstractDTOPagesRest(PollDao::class.java, "poll.title") { - override fun createListLayout( - request: HttpServletRequest, - layout: UILayout, - magicFilter: MagicFilter, - userAccess: UILayout.UserAccess - ) { - TODO("Not yet implemented") - } - - override fun transformFromDB(obj: PollDO, editMode: Boolean): Poll { - TODO("Not yet implemented") - } - - override fun transformForDB(dto: Poll): PollDO { - TODO("Not yet implemented") - } - - -} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 4f2b0d7b1b..bf0ebad33f 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -92,6 +92,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val fieldset = UIFieldset(UILength(12)) fieldset + .add(UIButton.createDefaultButton( + id = "response-poll-button", + responseAction = ResponseAction(PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "${dto.id}", targetType = TargetType.REDIRECT), + title = "poll.response.poll" + )) .add(lc, "title", "description", "location") .add(lc, "owner") .add(lc, "deadline", "date") diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt new file mode 100644 index 0000000000..d29cdfb1db --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt @@ -0,0 +1,38 @@ +package org.projectforge.rest.poll + +import com.fasterxml.jackson.databind.ObjectMapper +import org.projectforge.business.poll.PollDO +import org.projectforge.business.poll.PollResponseDO +import org.projectforge.framework.persistence.user.entities.PFUserDO +import org.projectforge.rest.dto.BaseDTO + +class PollResponse: BaseDTO() { + var poll: PollDO? = null + var owner: PFUserDO? = null + var responses: MutableList? = mutableListOf() + + override fun copyTo(dest: PollResponseDO) { + if (!this.responses.isNullOrEmpty()) { + dest.responses = ObjectMapper().writeValueAsString(this.responses) + } + super.copyTo(dest) + } + + override fun copyFrom(src: PollResponseDO) { + if (!src.responses.isNullOrEmpty()) { + val a = ObjectMapper().readValue(src.responses, MutableList::class.java) + this.responses = a.map { Answer().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() + } + super.copyFrom(src) + } +} + +class Answer { + var uid: String? = null + var questionUid: String? = "" + var answers: MutableList? = mutableListOf() + + fun toObject(string:String): Answer { + return ObjectMapper().readValue(string, Answer::class.java) + } +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt new file mode 100644 index 0000000000..7ef840c2c4 --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -0,0 +1,164 @@ +package org.projectforge.rest.poll + +import com.fasterxml.jackson.databind.ObjectMapper +import org.projectforge.business.poll.PollDO +import org.projectforge.business.poll.PollDao +import org.projectforge.business.poll.PollResponseDO +import org.projectforge.business.poll.PollResponseDao +import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext +import org.projectforge.framework.utils.NumberHelper +import org.projectforge.rest.config.Rest +import org.projectforge.rest.core.AbstractDynamicPageRest +import org.projectforge.rest.core.PagesResolver +import org.projectforge.rest.core.RestResolver +import org.projectforge.rest.dto.FormLayoutData +import org.projectforge.rest.dto.PostData +import org.projectforge.rest.poll.types.* +import org.projectforge.ui.* +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import java.util.UUID +import javax.servlet.http.HttpServletRequest + +@RestController +@RequestMapping("${Rest.URL}/response") +class ResponsePageRest : AbstractDynamicPageRest() { + + @Autowired + private lateinit var pollDao: PollDao + + @Autowired + private lateinit var pollResponseDao: PollResponseDao + + @GetMapping("dynamic") + fun getForm(request: HttpServletRequest, @RequestParam("id") pollStringId: String?): FormLayoutData { + val id = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") + val pollData = pollDao.internalGetById(id) ?: PollDO() + val pollDto = transformPollFromDB(pollData) + + val layout = UILayout("poll.response.title") + val fieldSet = UIFieldset(12, title = pollDto.title) + fieldSet + .add(UIReadOnlyField(value = pollDto.description, label = "Description")) + .add(UIReadOnlyField(value = pollDto.location, label = "Location")) + .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = "Owner")) + .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = "Deadline")) + + layout.add(fieldSet) + + val pollResponse = PollResponse() + pollResponse.poll = pollData + + pollResponseDao.internalLoadAll().firstOrNull { response -> + response.owner == ThreadLocalUserContext.user + && response.poll?.id == pollData.id + }?.let { + pollResponse.copyFrom(it) + } + + pollDto.inputFields?.forEachIndexed { index, field -> + val fieldSet2 = UIFieldset(title = field.question) + val answer = Answer() + answer.uid = UUID.randomUUID().toString() + answer.questionUid = field.uid + pollResponse.responses?.firstOrNull { + it.questionUid == field.uid + }.let { + if (it == null) + pollResponse.responses?.add(answer) + } + + val col = UICol() + + if (field.type == BaseType.TextQuestion) { + col.add(UITextArea("responses[$index].answers[0]")) + } + if (field.type == BaseType.YesNoQuestion) { + col.add( + UIRadioButton( + "responses[$index].answers[0]", + value = field.answers!![0], + label = field.answers?.get(0) ?: "" + ) + ) + col.add( + UIRadioButton( + "responses[$index].answers[0]", + value = field.answers!![1], + label = field.answers?.get(1) ?: "" + ) + ) + } + if (field.type == BaseType.DateQuestion) { + col.add(UITextArea("responses[$index].answers[0]")) + } + if (field.type == BaseType.MultipleChoices) { + field.answers?.forEachIndexed { index2, _ -> + col.add(UICheckbox("responses[$index].answers[$index2]", label = field.answers?.get(index2) ?: "")) + } + } + fieldSet2.add(UIRow().add(col)) + layout.add(fieldSet2) + } + + layout.add( + UIButton.createDefaultButton( + id = "doResponse", + title = "response", + responseAction = ResponseAction( + RestResolver.getRestUrl( + this::class.java, + "doResponse" + ), targetType = TargetType.POST + ) + ) + ) + + return FormLayoutData(pollResponse, layout, createServerData(request)) + } + + @PostMapping("doResponse") + fun doResponse( + request: HttpServletRequest, + @RequestBody postData: PostData + ): ResponseEntity? { + + val pollResponseDO = PollResponseDO() + postData.data.copyTo(pollResponseDO) + pollResponseDO.owner = ThreadLocalUserContext.user + + pollResponseDao.internalLoadAll().firstOrNull { pollResponse -> + pollResponse.owner == ThreadLocalUserContext.user + && pollResponse.poll?.id == postData.data.poll?.id + }?.let { + it.responses = pollResponseDO.responses + pollResponseDao.update(it) + return ResponseEntity.ok( + ResponseAction( + targetType = TargetType.REDIRECT, + url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + ) + ) + } + + pollResponseDao.saveOrUpdate(pollResponseDO) + return ResponseEntity.ok( + ResponseAction( + targetType = TargetType.REDIRECT, + url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + ) + ) + } + + + private fun transformPollFromDB(obj: PollDO): Poll { + val poll = Poll() + poll.copyFrom(obj) + if (obj.inputFields != null) { + val a = ObjectMapper().readValue(obj.inputFields, MutableList::class.java) + poll.inputFields = a.map { Question().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() + } + return poll + } +} \ No newline at end of file From e1ebb65bc4c7f56389dbec5c39d5b7ce507c4730 Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Fri, 21 Apr 2023 09:54:31 +0200 Subject: [PATCH 052/160] Some slight changes --- .../main/kotlin/org/projectforge/business/poll/PollDO.kt | 9 +++++++-- .../kotlin/org/projectforge/business/poll/PollStatus.kt | 2 +- .../kotlin/org/projectforge/rest/poll/PollPageRest.kt | 2 +- .../org/projectforge/rest/poll/ResponsePageRest.kt | 5 ----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 00efd006f6..5e230afef2 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -2,12 +2,17 @@ package org.projectforge.business.poll import org.hibernate.search.annotations.Indexed import org.projectforge.business.common.BaseUserGroupRightsDO +import org.projectforge.business.user.GroupDao +import org.projectforge.common.StringHelper import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.persistence.api.AUserRightId import org.projectforge.framework.persistence.entities.DefaultBaseDO import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.persistence.user.entities.PFUserDO +import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.DependsOn +import org.springframework.stereotype.Component +import org.springframework.stereotype.Service import java.time.LocalDate import javax.persistence.* @@ -80,9 +85,9 @@ open class PollDO : DefaultBaseDO() { @Transient fun getPollStatus(): PollStatus { return if (LocalDate.now().isAfter(deadline)) { - PollStatus.EXPIRED + PollStatus.FINISHED } else { - PollStatus.ACTIVE + PollStatus.RUNNING } } diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatus.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatus.kt index dd53fd7521..1569af9d3d 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatus.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatus.kt @@ -3,7 +3,7 @@ package org.projectforge.business.poll import org.projectforge.common.i18n.I18nEnum enum class PollStatus(val key: String): I18nEnum { - ACTIVE("active"), EXPIRED("expired"); + RUNNING("running"), FINISHED("finished"); override val i18nKey: String get() = "poll.$key" diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 5eeb3bae57..7769a51928 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -238,7 +238,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) elements.add( UIFilterListElement("status", label = translate("poll.status"), defaultFilter = true) - .buildValues(PollStatus.ACTIVE, PollStatus.EXPIRED) + .buildValues(PollStatus.RUNNING, PollStatus.FINISHED) ) } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 7ef840c2c4..992e6c537b 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -93,11 +93,6 @@ class ResponsePageRest : AbstractDynamicPageRest() { if (field.type == BaseType.DateQuestion) { col.add(UITextArea("responses[$index].answers[0]")) } - if (field.type == BaseType.MultipleChoices) { - field.answers?.forEachIndexed { index2, _ -> - col.add(UICheckbox("responses[$index].answers[$index2]", label = field.answers?.get(index2) ?: "")) - } - } fieldSet2.add(UIRow().add(col)) layout.add(fieldSet2) } From 5b5e8ec49999eb7b9f35db5163755b1bf5642e2a Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Fri, 21 Apr 2023 10:05:22 +0200 Subject: [PATCH 053/160] Fix Leons merge bug --- .../kotlin/org/projectforge/rest/poll/ResponsePageRest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 7ef840c2c4..1aabe2895b 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -74,7 +74,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { if (field.type == BaseType.TextQuestion) { col.add(UITextArea("responses[$index].answers[0]")) } - if (field.type == BaseType.YesNoQuestion) { + if (field.type == BaseType.SingleResponseQuestion) { col.add( UIRadioButton( "responses[$index].answers[0]", @@ -93,7 +93,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { if (field.type == BaseType.DateQuestion) { col.add(UITextArea("responses[$index].answers[0]")) } - if (field.type == BaseType.MultipleChoices) { + if (field.type == BaseType.MultiResponseQuestion) { field.answers?.forEachIndexed { index2, _ -> col.add(UICheckbox("responses[$index].answers[$index2]", label = field.answers?.get(index2) ?: "")) } From c761776f9187639be751037ebccedb03f7bc02b3 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 21 Apr 2023 10:06:43 +0200 Subject: [PATCH 054/160] begin to implement exel --- .../business/poll/PollResponseDO.kt | 1 + .../rest/poll/Exel/ExcelExport.kt | 63 ++++++++++--------- .../projectforge/rest/poll/PollPageRest.kt | 9 ++- 3 files changed, 41 insertions(+), 32 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt index c79f36557c..cbb4d3aede 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt @@ -14,6 +14,7 @@ import javax.persistence.* @AUserRightId(value = "poll.response", checkAccess = false) @DependsOn("org.projectforge.framework.persistence.user.entities.PFUserDO") open class PollResponseDO : DefaultBaseDO() { + @get:PropertyInfo(i18nKey = "poll.response.poll") @get:ManyToOne(fetch = FetchType.LAZY) @get:JoinColumn(name = "poll_pk", nullable = false) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt index 70c235f66a..3efaf8fa68 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt @@ -3,9 +3,14 @@ package org.projectforge.rest.poll.Exel import de.micromata.merlin.excel.ExcelRow import de.micromata.merlin.excel.ExcelSheet import de.micromata.merlin.excel.ExcelWorkbook +import org.projectforge.business.poll.PollResponseDO +import org.projectforge.business.poll.PollResponseDao +import org.projectforge.rest.dto.User import org.projectforge.rest.poll.Poll +import org.projectforge.rest.poll.PollResponse import org.slf4j.Logger import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired import org.springframework.core.io.ClassPathResource import java.io.IOException import java.time.LocalDate @@ -16,26 +21,28 @@ class ExcelExport { private val log: Logger = LoggerFactory.getLogger(ExcelExport::class.java) - private val FIRST_DATA_ROW_NUM = 5 + private val FIRST_DATA_ROW_NUM = 1 + @Autowired + lateinit var pollResponseDao: PollResponseDao + fun getExcel(poll: Poll): ByteArray? { + var responses = pollResponseDao.internalLoadAll().filter { it.poll?.id == poll.id } - fun getExcel(obj: Poll): ByteArray? { - //var excelSheet: ExcelSheet? = null - //var emptyRow: ExcelRow? = null - - val classPathResource = ClassPathResource("officeTemplates/PollResultTemplate" + ".xlsx") + val classPathResource = ClassPathResource("officeTemplates/PollResultTemplate" + ".xlsx") try { ExcelWorkbook(classPathResource.inputStream, classPathResource.file.name).use { workbook -> val excelSheet = workbook.getSheet(0) val emptyRow = excelSheet.getRow(5) - val anzNewRows = 3 - //excelSheet.getRow(0).getCell(0).setCellValue(contentOfCell) + val anzNewRows = poll.attendees!!.size createNewRow(excelSheet, emptyRow, anzNewRows) - var hourCounter = 0.0 - for (i in 0 until anzNewRows) { - hourCounter = setNewRows(hourCounter, excelSheet, i) + setFirstRow(excelSheet, poll) + poll.attendees?.sortedBy { it.displayName } + poll.attendees?.forEachIndexed { index, user -> + val res = PollResponse() + responses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } + setNewRows(excelSheet,poll, user, res, index) } return returnByteFile(excelSheet) } @@ -47,31 +54,27 @@ class ExcelExport { return null } - private fun setNewRows(hourCounter: Double, excelSheet: ExcelSheet, cell: Int): Double { - val hourCounter = hourCounter - val description: String = "" + private fun setFirstRow(excelSheet: ExcelSheet, poll: Poll){ + val excelRow = excelSheet.getRow(0) + poll.inputFields?.forEachIndexed{i, question -> + excelRow.getCell(i+1).setCellValue(question.question) + } + excelRow.setHeight(20F) + } + private fun setNewRows(excelSheet: ExcelSheet, poll:Poll, user: User, res:PollResponse?, index: Int) { - val excelRow = excelSheet.getRow(FIRST_DATA_ROW_NUM + cell) - excelRow.getCell(0).setCellValue("test1") - excelRow.getCell(1).setCellValue("test2") - excelRow.getCell(3).setCellValue("test3") - excelRow.getCell(4).setCellValue("test4") - excelRow.getCell(5).setCellValue("test5") + val excelRow = excelSheet.getRow(FIRST_DATA_ROW_NUM + index) - val puffer = description - var counterOfBreaking = 0 - var counterOfOverlength = 0 - val pufferSplit = puffer.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + excelRow.getCell(0).setCellValue(user.displayName) - // check for line-breaks - for (i in pufferSplit.indices) { - counterOfBreaking++ - counterOfOverlength += pufferSplit[i].length / 70 + poll.inputFields?.forEachIndexed{i, question -> + val answer = res?.responses?.find { it.uid == question.uid } + excelRow.getCell(i+1).setCellValue(answer?.answers.toString()) } - excelRow.setHeight((14 + counterOfOverlength * 14 + counterOfBreaking * 14).toFloat()) - return hourCounter + + excelRow.setHeight(20F) } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 6f461db607..ecb6646d81 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -39,6 +39,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @Autowired private lateinit var pollMailService: PollMailService + @Autowired + private lateinit var pollDao: PollDao /* @GetMapping("/edit/{id}") fun getForm(@RequestBody postData: PostData,request: HttpServletRequest, @RequestParam("id") pollStringId: String?): ResponseEntity { @@ -331,9 +333,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } - @PostMapping("Export") - fun export(request: HttpServletRequest,poll: Poll) : ResponseEntity? { + @PostMapping("/export/{id}") + fun export(request: HttpServletRequest ,@PathVariable("id") id: String) : ResponseEntity? { val ihkExporter = ExcelExport() + val poll = Poll() + var pollDo = pollDao.getById(id.toInt()) + poll.copyFrom(pollDo) val bytes: ByteArray? = ihkExporter .getExcel(poll) val filename = ("test.xlsx") From 606f769b4bff950393d032927a9569534e8bd32c Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Fri, 21 Apr 2023 11:14:23 +0200 Subject: [PATCH 055/160] i18n --- .../org/projectforge/business/poll/PollDO.kt | 6 ++++-- .../src/main/resources/I18nResources.properties | 11 +++++++++++ .../org/projectforge/rest/poll/PollPageRest.kt | 16 ++++++++-------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 9cd8733862..e612e9d97f 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -26,7 +26,7 @@ open class PollDO : DefaultBaseDO() { @get:Column(name = "description", length = 10000) open var description: String? = null - @get:PropertyInfo(i18nKey = "poll.owner", additionalI18nKey = "poll.owner.explaination") + @get:PropertyInfo(i18nKey = "poll.owner", additionalI18nKey = "poll.owner.explanation") @get:ManyToOne(fetch = FetchType.LAZY) @get:JoinColumn(name = "owner_pk", nullable = false) open var owner: PFUserDO? = null @@ -47,13 +47,15 @@ open class PollDO : DefaultBaseDO() { @get:Column(name = "attendeesIds", nullable = true) open var attendeesIds: String? = null - @PropertyInfo(i18nKey = "poll.group_attendees") + @PropertyInfo(i18nKey = "poll.attendee_groups") @get:Column(name = "groupAttendeesIds", nullable = true) open var groupAttendeesIds: String? = null + @PropertyInfo(i18nKey = "poll.full_access_groups") @get:Column(name = "full_access_group_ids", length = 4000, nullable = true) open var fullAccessGroupIds: String? = null + @PropertyInfo(i18nKey = "poll.full_access_user") @get:Column(name = "full_access_user_ids", length = 4000, nullable = true) open var fullAccessUserIds: String? = null diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index ef297a0939..0ceccf92c1 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -1973,6 +1973,17 @@ plugins.teamcal.title.add=Add calendar plugins.teamcal.title.edit=Edit team calendar plugins.teamcal.title.heading=Calendar plugins.teamcal.title.list=List of calendars +poll = Poll +poll.title = Title +poll.description = Description +poll.owner = Owner +poll.location = Location +poll.deadline = Deadline +poll.date = Date +poll.attendees = Attendees +poll.group_attendees = Attendee Groups + +poll.owner.explanation = The owner automatically have full access projectmanagement.personDays=Person days projectmanagement.personDays.short=pd question.deleteQuestion=Do your really want to delete this object finally? diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index de24525dc4..3013cb374a 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -254,10 +254,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. private fun addQuestionFieldset(layout: UILayout, dto: Poll) { dto.inputFields?.forEachIndexed { index, field -> - val objGiven = dto.id != null + val objGiven = dto.id != null //differentiates between initial creation and editing val fieldset = UIFieldset(UILength(12), title = field.type.toString()) - .add(generateDeleteButton(layout, field.uid)) - .add(getUiElement(objGiven, "inputFields[${index}].question", "Frage")) + if(!objGiven) + fieldset.add(generateDeleteButton(layout, field.uid)) + fieldset.add(getUiElement(objGiven, "inputFields[${index}].question", "Frage")) if (field.type == BaseType.YesNoQuestion) { val buttons = UIGroup() field.answers?.forEach { answer -> @@ -274,7 +275,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. if (field.type == BaseType.MultipleChoices) { val groupLayout = UIGroup() field.answers?.forEachIndexed { i, _ -> - groupLayout.add(UIInput("inputFields[${index}].answers[${i}]", label = "Antwortmöglichkeit ${i + 1}")) + groupLayout.add(getUiElement(objGiven, "inputFields[${index}].answers[${i}]", "Antwortmöglichkeit ${i + 1}")) } if(!objGiven) { groupLayout.add( @@ -286,11 +287,10 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) if (field.type == BaseType.MultipleChoices) { groupLayout.add( - UIInput( + getUiElement(objGiven, "inputFields[${index}].numberOfSelect", - dataType = UIDataType.INT, - label = "Wie viele sollen " + - "angeklickt werden können?" + "Wie viele sollen angeklickt werden können?", + UIDataType.INT, ) ) } From f6796eecc5c89087857ba5ca11ff23cc2c0dd199 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 21 Apr 2023 11:24:31 +0200 Subject: [PATCH 056/160] messge is missing but it worke --- .../org/projectforge/business/poll/PollDO.kt | 2 +- .../org/projectforge/rest/poll/PollPageRest.kt | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 8da63bb389..91d05af1fd 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -26,7 +26,7 @@ open class PollDO : DefaultBaseDO() { @get:Column(name = "description", length = 10000) open var description: String? = null - @get:PropertyInfo(i18nKey = "poll.owner", additionalI18nKey = "poll.owner.explaination") + @get:PropertyInfo(i18nKey = "poll.owner") @get:ManyToOne(fetch = FetchType.LAZY) @get:JoinColumn(name = "owner_pk", nullable = false) var owner: PFUserDO? = null diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index cd52f3d078..c53fca3ddd 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -158,7 +158,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. found?.answers?.add("") return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", + createEditLayout(dto, userAccess)) ) } @@ -199,22 +200,28 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val userAccess = UILayout.UserAccess() + var message = "" val groupIds = dto.groupAttendees?.filter{it.id != null}?.map{it.id!!}?.toIntArray() val userIds = UserService().getUserIds(groupService.getGroupUsers(groupIds)) val users = User.toUserList(userIds) User.restoreDisplayNames(users, userService) - val allUsers = users?.toMutableList()?: mutableListOf() + val allUsers = dto.attendees?.toMutableList()?: mutableListOf() - dto.attendees?.forEach { user -> + var counter = 0 ; + users?.forEach { user -> if(allUsers?.filter { it.id == user.id }?.isEmpty() == true) { allUsers.add(user) + counter ++ } } + + dto.groupAttendees = mutableListOf() dto.attendees = allUsers.sortedBy { it.displayName } return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE) + ResponseAction(targetType = TargetType.UPDATE + ) .addVariable("ui", createEditLayout(dto, userAccess)) .addVariable("data", dto) ) From a4b0cb84019c52cfe5d0654d5552a7827fdc4776 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 21 Apr 2023 11:32:05 +0200 Subject: [PATCH 057/160] remove message --- .../src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 386f2f5e2b..531e871023 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -206,7 +206,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val userAccess = UILayout.UserAccess() - var message = "" val groupIds = dto.groupAttendees?.filter{it.id != null}?.map{it.id!!}?.toIntArray() val userIds = UserService().getUserIds(groupService.getGroupUsers(groupIds)) val users = User.toUserList(userIds) From dd5e8214665149d59e0393128339a44e6d60febb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Fri, 21 Apr 2023 11:33:10 +0200 Subject: [PATCH 058/160] Added confirm message to 'Create' Button --- .../main/kotlin/org/projectforge/rest/poll/PollPageRest.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index bf0ebad33f..9628a8b7cd 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -149,7 +149,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) ) - return LayoutUtils.processEditPage(layout, dto, this) + var processedLayout = LayoutUtils.processEditPage(layout, dto, this) + processedLayout.actions.filterIsInstance().find { + it.id == "create" + }?.confirmMessage = "Willst du wirklich die Umfrage erstellen? Du kannst die Fragen im Nachhinein nicht mehr bearbeiten." + return processedLayout } From a41e194995d56204343ff5bf89d3f8a224f2b96f Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 21 Apr 2023 11:49:44 +0200 Subject: [PATCH 059/160] fix isAttendee --- .../org/projectforge/business/poll/PollDao.kt | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index adfb389d70..324042f8f7 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -26,6 +26,7 @@ open class PollDao : BaseDao(PollDO::class.java){ operationType: OperationType?, throwException: Boolean ): Boolean { + if (obj == null && operationType == OperationType.SELECT) { return true }; @@ -55,28 +56,12 @@ open class PollDao : BaseDao(PollDO::class.java){ fun isAttendee(obj: PollDO): Boolean { val loggedInUser = user - - var listOfAttendeesIds = ObjectMapper().readValue(obj.attendeesIds, IntArray::class.java) - + val listOfAttendeesIds = ObjectMapper().readValue(obj.attendeesIds, IntArray::class.java) if (loggedInUser != null) { if(listOfAttendeesIds.contains(loggedInUser.id)){ return true } } - - var stringBuilder = StringBuilder() - if (listOfAttendeesIds.isNotEmpty()){ - val groupUsers = groupService?.getGroupUsers(listOfAttendeesIds) - - groupUsers?.forEach{ - if (!listOfAttendeesIds.contains(it.id)){ - listOfAttendeesIds.plus(it.id) - } - } - if(groupUsers?.contains(loggedInUser) == true) - return true - } - obj.attendeesIds= ObjectMapper().writeValueAsString(listOfAttendeesIds) From 15038e126e2a94a3146da3416d0acb75626b927f Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 21 Apr 2023 11:51:51 +0200 Subject: [PATCH 060/160] delite empty peace --- .../src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 531e871023..aca3562cda 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -203,9 +203,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto: Poll, watchFieldsTriggered: Array? ): ResponseEntity { - val userAccess = UILayout.UserAccess() - val groupIds = dto.groupAttendees?.filter{it.id != null}?.map{it.id!!}?.toIntArray() val userIds = UserService().getUserIds(groupService.getGroupUsers(groupIds)) val users = User.toUserList(userIds) @@ -220,7 +218,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } } - dto.groupAttendees = mutableListOf() dto.attendees = allUsers.sortedBy { it.displayName } From 7c15d589f097088c3680f43d4d04b0df16a1c210 Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Fri, 21 Apr 2023 12:01:55 +0200 Subject: [PATCH 061/160] removed date question and fixed null owner bug --- .../projectforge/rest/poll/PollPageRest.kt | 20 ++----------------- .../rest/poll/ResponsePageRest.kt | 3 --- .../projectforge/rest/poll/types/Question.kt | 1 - 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 312387e941..52a44a5114 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -183,7 +183,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val found = dto.inputFields?.find { it.uid == fieldUid } found?.answers?.add("") - + dto.owner = userService.getUser(dto.owner?.id) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) @@ -204,9 +204,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. if(type == BaseType.SingleResponseQuestion) { question.answers = mutableListOf("ja", "nein") } - if(type == BaseType.DateQuestion) { - question.answers = mutableListOf("Ja", "Vielleicht", "Nein") - } dto.inputFields!!.add(question) dto.owner = userService.getUser(dto.owner?.id) @@ -261,19 +258,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. fieldset.add(groupLayout) } - if (field.type == BaseType.DateQuestion) { - fieldset - .add( - UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add( - getUiElement(objGiven, - "inputFields[${index}].question", - "Hast du am ... Zeit?" - ) - ) - - ) - } - layout.add(fieldset) } } @@ -311,7 +295,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val userAccess = UILayout.UserAccess(insert = true, update = true) dto.inputFields?.find { it.uid.equals(questionUid) }?.answers?.removeAt(answerIndex) - + dto.owner = userService.getUser(dto.owner?.id) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 1aabe2895b..fe682c41bc 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -90,9 +90,6 @@ class ResponsePageRest : AbstractDynamicPageRest() { ) ) } - if (field.type == BaseType.DateQuestion) { - col.add(UITextArea("responses[$index].answers[0]")) - } if (field.type == BaseType.MultiResponseQuestion) { field.answers?.forEachIndexed { index2, _ -> col.add(UICheckbox("responses[$index].answers[$index2]", label = field.answers?.get(index2) ?: "")) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt index 085fc28a86..185eb67171 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt @@ -24,5 +24,4 @@ enum class BaseType { TextQuestion, SingleResponseQuestion, MultiResponseQuestion, - DateQuestion } \ No newline at end of file From cc14a9ef9c2cf7ba1cd66d1e9e2bbaa4e906bed4 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 21 Apr 2023 12:53:17 +0200 Subject: [PATCH 062/160] fix --- .../business/poll/PollResponseDO.kt | 30 ++ .../business/poll/PollResponseDao.kt | 23 ++ .../projectforge/menu/builder/MenuCreator.kt | 2 + .../menu/builder/MenuItemDefId.kt | 1 + .../migrate/common/V7.5.1.2__7.5.1.0-POLL.sql | 20 +- .../projectforge/rest/poll/AntwortSeite.kt | 35 --- .../projectforge/rest/poll/PollPageRest.kt | 260 +++++++++++------- .../projectforge/rest/poll/PollResponse.kt | 38 +++ .../rest/poll/ResponsePageRest.kt | 164 +++++++++++ .../rest/poll/types/PremadeQuestions.kt | 19 +- .../projectforge/rest/poll/types/Question.kt | 8 +- 11 files changed, 446 insertions(+), 154 deletions(-) create mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt create mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt delete mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt new file mode 100644 index 0000000000..0f5399c391 --- /dev/null +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt @@ -0,0 +1,30 @@ +package org.projectforge.business.poll + +import org.hibernate.search.annotations.Indexed +import org.projectforge.common.anots.PropertyInfo +import org.projectforge.framework.persistence.api.AUserRightId +import org.projectforge.framework.persistence.entities.DefaultBaseDO +import org.projectforge.framework.persistence.user.entities.PFUserDO +import org.springframework.context.annotation.DependsOn +import javax.persistence.* + +@Entity +@Indexed +@Table(name = "t_poll_response") +@AUserRightId(value = "poll.response", checkAccess = false) +@DependsOn("org.projectforge.framework.persistence.user.entities.PFUserDO") +open class PollResponseDO : DefaultBaseDO() { + @get:PropertyInfo(i18nKey = "poll.response.poll") + @get:ManyToOne(fetch = FetchType.LAZY) + @get:JoinColumn(name = "poll_fk", nullable = false) + open var poll: PollDO? = null + + @get:PropertyInfo(i18nKey = "poll.response.owner") + @get:ManyToOne(fetch = FetchType.LAZY) + @get:JoinColumn(name = "owner_fk", nullable = false) + open var owner: PFUserDO? = null + + @PropertyInfo(i18nKey = "poll.responses") + @get:Column(name = "responses", nullable = true, length = 1000) + open var responses: String? = null +} \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt new file mode 100644 index 0000000000..66cd987917 --- /dev/null +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt @@ -0,0 +1,23 @@ +package org.projectforge.business.poll + +import org.projectforge.framework.access.OperationType +import org.projectforge.framework.persistence.api.BaseDao +import org.projectforge.framework.persistence.user.entities.PFUserDO +import org.springframework.stereotype.Repository + +@Repository +open class PollResponseDao : BaseDao(PollResponseDO::class.java) { + override fun newInstance(): PollResponseDO { + return PollResponseDO() + } + + override fun hasAccess( + user: PFUserDO?, + obj: PollResponseDO?, + oldObj: PollResponseDO?, + operationType: OperationType?, + throwException: Boolean + ): Boolean { + return true + } +} \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuCreator.kt b/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuCreator.kt index c7d60e11b3..c86ef61179 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuCreator.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuCreator.kt @@ -36,6 +36,7 @@ import org.projectforge.business.orga.ContractDao import org.projectforge.business.orga.PostausgangDao import org.projectforge.business.orga.PosteingangDao import org.projectforge.business.orga.VisitorbookDao +import org.projectforge.business.poll.PollDao import org.projectforge.business.sipgate.SipgateConfiguration import org.projectforge.business.user.ProjectForgeGroup import org.projectforge.business.user.UserRightValue @@ -449,6 +450,7 @@ open class MenuCreator { requiredUserRightId = ContractDao.USER_RIGHT_ID, requiredUserRightValues = READONLY_READWRITE ) ) + .add(MenuItemDef(MenuItemDefId.POLL)) .add( MenuItemDef( MenuItemDefId.VISITORBOOK, diff --git a/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuItemDefId.kt b/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuItemDefId.kt index 677f89bcb5..b105dbdc39 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuItemDefId.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuItemDefId.kt @@ -86,6 +86,7 @@ enum class MenuItemDefId constructor(val i18nKey: String, val url: String? = nul OUTGOING_INVOICE_LIST("menu.fibu.rechnungen", "wa/outgoingInvoiceList"), // PERSONAL_STATISTICS("menu.personalStatistics", "wa/personalStatistics"), // PHONE_CALL("menu.phoneCall", "wa/phoneCall"), // + POLL("menu.poll", getReactListUrl("poll")), // PROJECT_LIST("menu.fibu.projekte", getReactListUrl("project")), // REPORT_OBJECTIVES("menu.fibu.reporting.reportObjectives", "wa/reportObjectives"), // SEND_SMS("menu.sendSms", "wa/sendSms"), // diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql index 5005a6f510..c16b83fe96 100644 --- a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql @@ -23,4 +23,22 @@ CREATE TABLE T_POLL ALTER TABLE T_POLL ADD CONSTRAINT t_poll_pkey PRIMARY KEY (pk); ALTER TABLE T_POLL - ADD CONSTRAINT fk_t_poll_pf_user FOREIGN KEY (owner_fk) REFERENCES t_pf_user (pk); \ No newline at end of file + ADD CONSTRAINT fk_t_poll_pf_user FOREIGN KEY (owner_fk) REFERENCES t_pf_user (pk); + +CREATE TABLE T_POLL_RESPONSE +( + pk INTEGER NOT NULL, + deleted BOOLEAN NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE, + last_update TIMESTAMP WITHOUT TIME ZONE, + poll_fk INTEGER NOT NULL, + owner_fk INTEGER NOT NULL, + responses CHARACTER Varying(10000) +); + +ALTER TABLE T_POLL_RESPONSE + ADD CONSTRAINT t_poll_response_pkey PRIMARY KEY (pk); +ALTER TABLE T_POLL_RESPONSE + ADD CONSTRAINT fk_t_poll_response_pf_user FOREIGN KEY (owner_fk) REFERENCES t_pf_user (pk); +ALTER TABLE T_POLL_RESPONSE + ADD CONSTRAINT fk_t_poll_response_poll FOREIGN KEY (poll_fk) REFERENCES t_poll (pk); \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt deleted file mode 100644 index 74d0c15ce1..0000000000 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/AntwortSeite.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.projectforge.rest.poll - -import org.projectforge.business.poll.PollDO -import org.projectforge.business.poll.PollDao -import org.projectforge.framework.persistence.api.MagicFilter -import org.projectforge.rest.config.Rest -import org.projectforge.rest.core.AbstractDTOPagesRest -import org.projectforge.ui.UILayout -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController -import javax.servlet.http.HttpServletRequest - - -@RestController -@RequestMapping("${Rest.URL}/poll/antwort") -class AntwortSeite : AbstractDTOPagesRest(PollDao::class.java, "poll.title") { - override fun createListLayout( - request: HttpServletRequest, - layout: UILayout, - magicFilter: MagicFilter, - userAccess: UILayout.UserAccess - ) { - TODO("Not yet implemented") - } - - override fun transformFromDB(obj: PollDO, editMode: Boolean): Poll { - TODO("Not yet implemented") - } - - override fun transformForDB(dto: Poll): PollDO { - TODO("Not yet implemented") - } - - -} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 1a120ffafc..f5265dd191 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -90,55 +90,69 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val poll = PollDO() dto.copyTo(poll) val layout = super.createEditLayout(dto, userAccess) - - layout.add( + + val fieldset = UIFieldset(UILength(12)) + + fieldset.add( UIRow().add( - UICol( - UILength(11) - ) + UICol( + UILength(10) + ) + ).add( + UICol( + UILength(1) ).add( - UICol( - UILength(1) - ).add( - UIButton.createLinkButton( - id = "pollDirections", responseAction = ResponseAction( - PagesResolver.getDynamicPageUrl( - PollInfoPageRest::class.java, absolute = true - ), targetType = TargetType.MODAL - ) - ) + UIButton.createLinkButton( + id = "poll-guide",title= "Poll Guide", responseAction = ResponseAction( + PagesResolver.getDynamicPageUrl( + PollInfoPageRest::class.java, absolute = true + ), targetType = TargetType.MODAL ) - ) - ) - - layout.add( - UIRow().add( - UIFieldset(UILength(md = 6, lg = 4)).add(lc, "title", "description", "location") - .add(lc, "owner") - .add(lc, "deadline", "date") - .add(UISelect.createUserSelect(lc, "fullAccessUsers", true, "poll.fullAccessUsers")) - .add(UISelect.createGroupSelect(lc, "fullAccessGroups", true, "poll.fullAccessGroups")) - .add(UISelect.createUserSelect(lc, "attendees", true, "poll.attendees")) - .add(UISelect.createGroupSelect(lc, "groupAttendees", true, "poll.groupAttendees")) - ) - ) - layout.add( - UIRow().add( - UIFieldset(UILength(md = 6, lg = 4)).add( - UIButton.createDefaultButton( - id = "add-question-button", - responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST), - title = "Eigene Frage hinzufügen" ) - ).add( - UISelect("questionType", values = BaseType.values().map { UISelectValue(it, it.name) }) ) ) - ) + .add( + UICol(UILength(1) + ).add(UIButton.createDefaultButton( + id = "response-poll-button", + responseAction = ResponseAction(PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "${dto.id}", targetType = TargetType.REDIRECT), + title = "Poll Response " + ))) + ) - layout.add( - UIRow().add( - UIFieldset(UILength(md = 6, lg = 4)).add( + + fieldset + .add(lc, "title", "description", "location") + .add(lc, "owner") + .add(lc, "deadline", "date") + .add(UISelect.createUserSelect(lc, "fullAccessUsers", true, "poll.fullAccessUsers")) + .add(UISelect.createGroupSelect(lc, "fullAccessGroups", true, "poll.fullAccessGroups")) + .add(UISelect.createUserSelect(lc, "attendees", true, "poll.attendees")) + .add(UISelect.createGroupSelect(lc, "groupAttendees", true, "poll.groupAttendees")) + .add( + UIRow() + .add( + UICol(UILength(xs = 9, sm = 9, md = 9, lg = 9)) + .add(UISelect("questionType", values = BaseType.values().map { UISelectValue(it, it.name) }, label = "questionType")) + ) + .add( + UICol(UILength(xs = 3, sm = 3, md = 3, lg = 3)) + .add( + UIButton.createDefaultButton( + id = "add-question-button", + responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST), + title = "Eigene Frage hinzufügen" + ) + ) + ) + ) + .add( + UIRow() + .add( + UICol(UILength(xs = 9, sm = 9, md = 9, lg = 9)) + ) + .add( + UICol(UILength(xs = 3, sm = 3, md = 3, lg = 3)).add( UIButton.createDefaultButton( id = "micromata-vorlage-button", responseAction = ResponseAction("${Rest.URL}/poll/addPremadeQuestions", targetType = TargetType.POST), @@ -147,22 +161,23 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) ) ) + layout.add(fieldset) + addQuestionFieldset(layout, dto) return LayoutUtils.processEditPage(layout, dto, this) } - //TODO refactor this whole file into multiple smaller files + override fun onAfterSaveOrUpdate(request: HttpServletRequest, poll: PollDO, postData: PostData) { super.onAfterSaveOrUpdate(request, poll, postData) - val dto = postData.data pollMailService.sendMail(subject = "", content = "", to = "test.mail") } - @PostMapping("/addAntwort/{fieldId}") - fun addAntwortFeld( + @PostMapping("/addAnswer/{fieldId}") + fun addAnswerForMultipleChoice( @RequestBody postData: PostData, @PathVariable("fieldId") fieldUid: String, ): ResponseEntity { @@ -190,7 +205,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ): ResponseEntity { val userAccess = UILayout.UserAccess(insert = true, update = true) val dto = postData.data - val poll = PollDO() //TODO: owner is empty,why? Only id is set if(dto.owner?.lastname == null || dto.owner?.firstname == null){ @@ -201,7 +215,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val type = BaseType.valueOf(dto.questionType ?: "TextQuestion") val question = Question(uid = UUID.randomUUID().toString(), type = type) - if(type == BaseType.YesNoQuestion) { + if(type == BaseType.SingleResponseQuestion) { question.answers = mutableListOf("ja", "nein") } if(type == BaseType.DateQuestion) { @@ -210,7 +224,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.inputFields!!.add(question) - dto.copyTo(poll) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) @@ -223,13 +236,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ): ResponseEntity { val userAccess = UILayout.UserAccess(insert = true, update = true) val dto = postData.data - val poll = PollDO() PREMADE_QUESTIONS.entries.forEach { entry -> dto.inputFields?.add(entry.value) } - dto.copyTo(poll) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) @@ -238,67 +249,115 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. private fun addQuestionFieldset(layout: UILayout, dto: Poll) { dto.inputFields?.forEachIndexed { index, field -> - val row = UIRow() - if (field.type == BaseType.YesNoQuestion) { - val groupLayout = UIGroup() - field.answers?.forEach { answer -> - groupLayout.add( - UIRadioButton( - "YesNoQuestion[${index}].question", answer, label = answer - ) - ) - } - row.add( - UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add(UIInput("inputFields[${index}].question")).add - (groupLayout) - ) - } + val fieldset = UIFieldset(UILength(12), title = field.type.toString()) + .add(generateDeleteButton(layout, field.uid)) + .add(UIInput("inputFields[${index}].question", label = "Frage")) - if (field.type == BaseType.TextQuestion) { - row.add( - UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add(UIInput("inputFields[${index}].question")) - ) - } - - if (field.type == BaseType.MultipleChoices || field.type == BaseType.DropDownQuestion) { - val f = UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()) - .add(UIInput("inputFields[${index}].question", label = "Die Frage")) - field.answers?.forEachIndexed { i, _ -> - f.add(UIInput("inputFields[${index}].answers[${i}]", label = "Antwortmöglichkeit ${i + 1}")) + if (field.type == BaseType.SingleResponseQuestion || field.type == BaseType.MultiResponseQuestion) { + val groupLayout = UIGroup() + field.answers?.forEachIndexed { answerIndex, _ -> + groupLayout.add(generateSingleAndMultiResponseAnswer(index, field.uid, answerIndex, layout)) } - f.add( - UIButton.createAddButton( - responseAction = ResponseAction( - "${Rest.URL}/poll/addAntwort/${field.uid}", targetType = TargetType.POST + groupLayout.add( + UIRow().add( + UIButton.createAddButton( + responseAction = ResponseAction( + "${Rest.URL}/poll/addAnswer/${field.uid}", targetType = TargetType.POST + ) ) ) ) - if (field.type == BaseType.MultipleChoices) { - f.add( - UIInput( - "inputFields[${index}].numberOfSelect", dataType = UIDataType.INT, label = "Wie viele sollen " + - "angeklickt werden können?" - ) - ) - } - row.add(f) + fieldset.add(groupLayout) } if (field.type == BaseType.DateQuestion) { - row.add( - UIFieldset(UILength(md = 6, lg = 4), title = field.type.toString()).add( + fieldset + .add( UIInput( "inputFields[${index}].question", label = "Hast du am ... Zeit?" ) ) - ) } - layout.add(row) + layout.add(fieldset) } } + private fun generateSingleAndMultiResponseAnswer(inputFieldIndex: Int, questionUid: String?, answerIndex: Int, layout: UILayout):UIRow { + val row = UIRow() + row.add( + UICol() + .add( + UIInput("inputFields[${inputFieldIndex}].answers[${answerIndex}]", label = "Answer ${answerIndex + 1}") + ) + ) + .add( + UICol() + .add( + UIButton.createDangerButton( + id = "X", + responseAction = ResponseAction( + "${Rest.URL}/poll/deleteAnswer/${questionUid}/${answerIndex}", targetType = TargetType.POST + ) + ).withConfirmMessage(layout, confirmMessage = "Willst du wirklich diese Antwort löschen?")) + ) + return row + } + + + @PostMapping("/deleteAnswer/{questionUid}/{answerIndex}") + fun deleteAnswerOfSingleAndMultipleResponseQuestion( + @RequestBody postData: PostData, + @PathVariable("questionUid") questionUid: String, + @PathVariable("answerIndex") answerIndex: Int + ): ResponseEntity { + val dto = postData.data + val userAccess = UILayout.UserAccess(insert = true, update = true) + + dto.inputFields?.find { it.uid.equals(questionUid) }?.answers?.removeAt(answerIndex) + + return ResponseEntity.ok( + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ) + } + + + private fun generateDeleteButton(layout: UILayout, uid:String?):UIRow { + val row = UIRow() + row.add( + UICol(UILength(11)) + ) + .add( + UICol(length = UILength(1)) + .add( + UIButton.createDangerButton( + id = "X", + responseAction = ResponseAction( + "${Rest.URL}/poll/deleteQuestion/${uid}", targetType = TargetType.POST + ) + ).withConfirmMessage(layout, confirmMessage = "Willst du wirklich diese Frage löschen?")) + ) + return row + } + + + @PostMapping("/deleteQuestion/{uid}") + fun deleteQuestion( + @RequestBody postData: PostData, + @PathVariable("uid") uid: String, + ): ResponseEntity { + val dto = postData.data + val userAccess = UILayout.UserAccess(insert = true, update = true) + + val matchingQuestion: Question? = dto.inputFields?.find { it.uid.equals(uid) } + dto.inputFields?.remove(matchingQuestion) + + return ResponseEntity.ok( + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ) + } + @PostMapping("Export") fun export(request: HttpServletRequest,poll: Poll) : ResponseEntity? { @@ -314,17 +373,4 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return RestUtils.downloadFile(filename, bytes) } - // create a update layout funktion, welche das layout nummr updatet und zurück gibt es soll für jeden Frage Basistyp eine eigene funktion haben - - - /*dto.inputFields?.forEachIndexed { field, index -> - if (field.type == msc) { - layout.add() // - "type[$index]" - Id: name - } - } - layout.add(UIRow().add(UIFieldset(UILength(md = 6, lg = 4)) - .add(lc, "name"))) - */ } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt new file mode 100644 index 0000000000..d29cdfb1db --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt @@ -0,0 +1,38 @@ +package org.projectforge.rest.poll + +import com.fasterxml.jackson.databind.ObjectMapper +import org.projectforge.business.poll.PollDO +import org.projectforge.business.poll.PollResponseDO +import org.projectforge.framework.persistence.user.entities.PFUserDO +import org.projectforge.rest.dto.BaseDTO + +class PollResponse: BaseDTO() { + var poll: PollDO? = null + var owner: PFUserDO? = null + var responses: MutableList? = mutableListOf() + + override fun copyTo(dest: PollResponseDO) { + if (!this.responses.isNullOrEmpty()) { + dest.responses = ObjectMapper().writeValueAsString(this.responses) + } + super.copyTo(dest) + } + + override fun copyFrom(src: PollResponseDO) { + if (!src.responses.isNullOrEmpty()) { + val a = ObjectMapper().readValue(src.responses, MutableList::class.java) + this.responses = a.map { Answer().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() + } + super.copyFrom(src) + } +} + +class Answer { + var uid: String? = null + var questionUid: String? = "" + var answers: MutableList? = mutableListOf() + + fun toObject(string:String): Answer { + return ObjectMapper().readValue(string, Answer::class.java) + } +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt new file mode 100644 index 0000000000..1aabe2895b --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -0,0 +1,164 @@ +package org.projectforge.rest.poll + +import com.fasterxml.jackson.databind.ObjectMapper +import org.projectforge.business.poll.PollDO +import org.projectforge.business.poll.PollDao +import org.projectforge.business.poll.PollResponseDO +import org.projectforge.business.poll.PollResponseDao +import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext +import org.projectforge.framework.utils.NumberHelper +import org.projectforge.rest.config.Rest +import org.projectforge.rest.core.AbstractDynamicPageRest +import org.projectforge.rest.core.PagesResolver +import org.projectforge.rest.core.RestResolver +import org.projectforge.rest.dto.FormLayoutData +import org.projectforge.rest.dto.PostData +import org.projectforge.rest.poll.types.* +import org.projectforge.ui.* +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import java.util.UUID +import javax.servlet.http.HttpServletRequest + +@RestController +@RequestMapping("${Rest.URL}/response") +class ResponsePageRest : AbstractDynamicPageRest() { + + @Autowired + private lateinit var pollDao: PollDao + + @Autowired + private lateinit var pollResponseDao: PollResponseDao + + @GetMapping("dynamic") + fun getForm(request: HttpServletRequest, @RequestParam("id") pollStringId: String?): FormLayoutData { + val id = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") + val pollData = pollDao.internalGetById(id) ?: PollDO() + val pollDto = transformPollFromDB(pollData) + + val layout = UILayout("poll.response.title") + val fieldSet = UIFieldset(12, title = pollDto.title) + fieldSet + .add(UIReadOnlyField(value = pollDto.description, label = "Description")) + .add(UIReadOnlyField(value = pollDto.location, label = "Location")) + .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = "Owner")) + .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = "Deadline")) + + layout.add(fieldSet) + + val pollResponse = PollResponse() + pollResponse.poll = pollData + + pollResponseDao.internalLoadAll().firstOrNull { response -> + response.owner == ThreadLocalUserContext.user + && response.poll?.id == pollData.id + }?.let { + pollResponse.copyFrom(it) + } + + pollDto.inputFields?.forEachIndexed { index, field -> + val fieldSet2 = UIFieldset(title = field.question) + val answer = Answer() + answer.uid = UUID.randomUUID().toString() + answer.questionUid = field.uid + pollResponse.responses?.firstOrNull { + it.questionUid == field.uid + }.let { + if (it == null) + pollResponse.responses?.add(answer) + } + + val col = UICol() + + if (field.type == BaseType.TextQuestion) { + col.add(UITextArea("responses[$index].answers[0]")) + } + if (field.type == BaseType.SingleResponseQuestion) { + col.add( + UIRadioButton( + "responses[$index].answers[0]", + value = field.answers!![0], + label = field.answers?.get(0) ?: "" + ) + ) + col.add( + UIRadioButton( + "responses[$index].answers[0]", + value = field.answers!![1], + label = field.answers?.get(1) ?: "" + ) + ) + } + if (field.type == BaseType.DateQuestion) { + col.add(UITextArea("responses[$index].answers[0]")) + } + if (field.type == BaseType.MultiResponseQuestion) { + field.answers?.forEachIndexed { index2, _ -> + col.add(UICheckbox("responses[$index].answers[$index2]", label = field.answers?.get(index2) ?: "")) + } + } + fieldSet2.add(UIRow().add(col)) + layout.add(fieldSet2) + } + + layout.add( + UIButton.createDefaultButton( + id = "doResponse", + title = "response", + responseAction = ResponseAction( + RestResolver.getRestUrl( + this::class.java, + "doResponse" + ), targetType = TargetType.POST + ) + ) + ) + + return FormLayoutData(pollResponse, layout, createServerData(request)) + } + + @PostMapping("doResponse") + fun doResponse( + request: HttpServletRequest, + @RequestBody postData: PostData + ): ResponseEntity? { + + val pollResponseDO = PollResponseDO() + postData.data.copyTo(pollResponseDO) + pollResponseDO.owner = ThreadLocalUserContext.user + + pollResponseDao.internalLoadAll().firstOrNull { pollResponse -> + pollResponse.owner == ThreadLocalUserContext.user + && pollResponse.poll?.id == postData.data.poll?.id + }?.let { + it.responses = pollResponseDO.responses + pollResponseDao.update(it) + return ResponseEntity.ok( + ResponseAction( + targetType = TargetType.REDIRECT, + url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + ) + ) + } + + pollResponseDao.saveOrUpdate(pollResponseDO) + return ResponseEntity.ok( + ResponseAction( + targetType = TargetType.REDIRECT, + url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + ) + ) + } + + + private fun transformPollFromDB(obj: PollDO): Poll { + val poll = Poll() + poll.copyFrom(obj) + if (obj.inputFields != null) { + val a = ObjectMapper().readValue(obj.inputFields, MutableList::class.java) + poll.inputFields = a.map { Question().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() + } + return poll + } +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt index 7e5f69784c..d1e47339cc 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt @@ -1,36 +1,43 @@ package org.projectforge.rest.poll.types +import java.util.UUID + val PREMADE_QUESTIONS = mapOf( "HAS_FOOD" to Question( + uid = UUID.randomUUID().toString(), question = "Was willst du essen?", - type = BaseType.MultipleChoices, - answers = mutableListOf("Fleisch", "Vegetarisch", "Vegan"), - numberOfSelect = 1 + type = BaseType.SingleResponseQuestion, + answers = mutableListOf("Fleisch", "Vegetarisch", "Vegan") ), "IS_REMOTE" to Question( + uid = UUID.randomUUID().toString(), question = "Nimmst du remote teil?", - type = BaseType.YesNoQuestion, + type = BaseType.SingleResponseQuestion, answers = mutableListOf("Ja", "Nein") ), "CAN_HAVE_COMPANIONS" to Question( + uid = UUID.randomUUID().toString(), question = "Nimmst du eine Begleitung mit? (Name der Begleitung)", type = BaseType.TextQuestion, answers = mutableListOf("") ), "CAN_HAVE_CHILDREN" to Question( + uid = UUID.randomUUID().toString(), question = "Nimmst du ein Kind mit? (Name der Begleitung)", type = BaseType.TextQuestion, answers = mutableListOf("") ), "CAN_STAY_OVERNIGHT" to Question( + uid = UUID.randomUUID().toString(), question = "Willst du dort übernachten?", - type = BaseType.YesNoQuestion, + type = BaseType.SingleResponseQuestion, answers = mutableListOf("Ja", "Nein") ), "HAS_BREAKFAST" to Question( + uid = UUID.randomUUID().toString(), question = "Willst du am nächsten Tag frühstücken?", - type = BaseType.YesNoQuestion, + type = BaseType.SingleResponseQuestion, answers = mutableListOf("Ja", "Nein") ), ) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt index 388f6bd272..085fc28a86 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt @@ -21,10 +21,8 @@ class Question( } enum class BaseType { - YesNoQuestion, - DateQuestion, - MultipleChoices, TextQuestion, - DropDownQuestion, - PremadeQuestion + SingleResponseQuestion, + MultiResponseQuestion, + DateQuestion } \ No newline at end of file From fc30afaf76806dd31a6773618e163451abd437af Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Fri, 21 Apr 2023 12:54:26 +0200 Subject: [PATCH 063/160] removed explanation --- .../src/main/kotlin/org/projectforge/business/poll/PollDO.kt | 2 +- .../src/main/resources/I18nResources.properties | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index e612e9d97f..1a6417b667 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -26,7 +26,7 @@ open class PollDO : DefaultBaseDO() { @get:Column(name = "description", length = 10000) open var description: String? = null - @get:PropertyInfo(i18nKey = "poll.owner", additionalI18nKey = "poll.owner.explanation") + @get:PropertyInfo(i18nKey = "poll.owner") @get:ManyToOne(fetch = FetchType.LAZY) @get:JoinColumn(name = "owner_pk", nullable = false) open var owner: PFUserDO? = null diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index 0ceccf92c1..50ae10de50 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -1982,8 +1982,6 @@ poll.deadline = Deadline poll.date = Date poll.attendees = Attendees poll.group_attendees = Attendee Groups - -poll.owner.explanation = The owner automatically have full access projectmanagement.personDays=Person days projectmanagement.personDays.short=pd question.deleteQuestion=Do your really want to delete this object finally? From 31edbd9d744c4c2de7a28b385fcc361bcef4c982 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 21 Apr 2023 13:06:15 +0200 Subject: [PATCH 064/160] refactore PR --- .../org/projectforge/rest/poll/PollInfoPageRest.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt index cbd53bba2e..3fbff669bb 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt @@ -35,9 +35,8 @@ class PollInfoPageRest: AbstractDynamicPageRest() { field.add(UICol() .add(UIReadOnlyField("deadline", label = "deadline", value = "deadline"))) - field.add(UIRow().add(UICol().add(UILabel(""" Anschließend werden die Fragen der Umfrage angelegt. - Die Fragen können aus verschiedenen Typen bestehen. - """)))) + field.add(UIRow().add(UICol().add(UILabel("""Anschließend werden die Fragen der Umfrage angelegt. + Die Fragen können aus verschiedenen Typen bestehen. """)))) layout.add(field) @@ -51,19 +50,19 @@ class PollInfoPageRest: AbstractDynamicPageRest() { )) layout.add(UIFieldset().add(UILabel("TextQuestion")).add( UICol() - .add(UIReadOnlyField("question", label = "Question", value = """Eine Frage die mit einer Freitext Antwort Beantwortet werden kann""".trimMargin())) + .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage die mit einer Freitext Antwort Beantwortet werden kann")) )) layout.add(UIFieldset().add(UILabel("DateQuestion")).add( UICol() .add(UIReadOnlyField("question", label = "Question", value = """Eine Frage ob an einem Tag (Uhrzeit) die Teilnehmer zeit haben. Die einem Ja, Nein oder Vielleicht - Beantwortet werden kann""".trimMargin() + Beantwortet werden kann""" )))) layout.add(UIFieldset().add(UILabel("Dropdown")).add( UICol() - .add(UIReadOnlyField("question", label = "Question", value = """ Eine Frage die mit einem Dropdown Beantwortet werden - | kann""".trimMargin() + .add(UIReadOnlyField("question", label = "Question", + value = """ Eine Frage die mit einem Dropdown Beantwortet werden kann""" )))) From 67ce06e44ed0888e71bbf399d237772975ee33cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Fri, 21 Apr 2023 13:35:04 +0200 Subject: [PATCH 065/160] Put answers in own row --- .../kotlin/org/projectforge/rest/poll/PollPageRest.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 52a44a5114..58128c149f 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -240,12 +240,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. fieldset.add(getUiElement(objGiven, "inputFields[${index}].question", "Frage")) if (field.type == BaseType.SingleResponseQuestion || field.type == BaseType.MultiResponseQuestion) { - val groupLayout = UIGroup() field.answers?.forEachIndexed { answerIndex, _ -> - groupLayout.add(generateSingleAndMultiResponseAnswer(objGiven, index, field.uid, answerIndex, layout)) + fieldset.add(generateSingleAndMultiResponseAnswer(objGiven, index, field.uid, answerIndex, layout)) } if(!objGiven) { - groupLayout.add( + fieldset.add( UIRow().add( UIButton.createAddButton( responseAction = ResponseAction( @@ -255,7 +254,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) ) } - fieldset.add(groupLayout) } layout.add(fieldset) @@ -279,7 +277,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. responseAction = ResponseAction( "${Rest.URL}/poll/deleteAnswer/${questionUid}/${answerIndex}", targetType = TargetType.POST ) - ).withConfirmMessage(layout, confirmMessage = "Willst du wirklich diese Antwortmöglichkeit löschen?")))} + ).withConfirmMessage(layout, confirmMessage = "Willst du wirklich diese Antwortmöglichkeit löschen?"))) + } return row } @@ -352,7 +351,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } //once created, questions should be ReadOnly - fun getUiElement(obj: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement{ + private fun getUiElement(obj: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement{ if (obj) return UIReadOnlyField(id, label = label, dataType = dataType) else From 25185faa07204f8135e899b24be89fa28ecf6b0b Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Fri, 21 Apr 2023 16:00:47 +0200 Subject: [PATCH 066/160] Add Attendee and Access filter. Remove "s" from attendeesIds and groupAttendeesIds. --- .../business/poll/PollAssignmentFilter.kt | 8 ++- .../org/projectforge/business/poll/PollDO.kt | 61 +++++++++++-------- .../org/projectforge/business/poll/PollDao.kt | 6 +- .../poll/{PollStatus.kt => PollState.kt} | 2 +- .../business/poll/PollStatusFilter.kt | 2 +- .../migrate/common/V7.5.1.2__7.5.1.0-POLL.sql | 4 +- .../kotlin/org/projectforge/rest/poll/Poll.kt | 8 +-- .../projectforge/rest/poll/PollPageRest.kt | 13 +--- .../rest/poll/ResponsePageRest.kt | 2 +- 9 files changed, 58 insertions(+), 48 deletions(-) rename projectforge-business/src/main/kotlin/org/projectforge/business/poll/{PollStatus.kt => PollState.kt} (79%) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignmentFilter.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignmentFilter.kt index 2a11ce4bd6..8c8675e283 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignmentFilter.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignmentFilter.kt @@ -5,6 +5,12 @@ import org.projectforge.framework.persistence.api.impl.CustomResultFilter class PollAssignmentFilter(val values: List): CustomResultFilter { override fun match(list: MutableList, element: PollDO): Boolean { - return values.contains(element.getPollAssignment()) + + element.getPollAssignment().forEach { pollAssignment -> + if (values.contains(pollAssignment)) { + return true + } + } + return false } } \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 47cdc06095..9b0638dc4f 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -1,22 +1,17 @@ package org.projectforge.business.poll import org.hibernate.search.annotations.Indexed -import org.projectforge.business.common.BaseUserGroupRightsDO -import org.projectforge.business.user.GroupDao -import org.projectforge.common.StringHelper import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.persistence.api.AUserRightId import org.projectforge.framework.persistence.entities.DefaultBaseDO import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.persistence.user.entities.PFUserDO -import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.DependsOn -import org.springframework.stereotype.Component -import org.springframework.stereotype.Service import java.time.LocalDate import javax.persistence.* +@Suppress("UNREACHABLE_CODE") @Entity @Indexed @Table(name = "t_poll") @@ -50,44 +45,60 @@ open class PollDO : DefaultBaseDO() { open var date: LocalDate? = null @PropertyInfo(i18nKey = "poll.attendees") - @get:Column(name = "attendeesIds", nullable = true) - open var attendeesIds: String? = null + @get:Column(name = "attendeeIds") + open var attendeeIds: String? = null @PropertyInfo(i18nKey = "poll.group_attendees") - @get:Column(name = "groupAttendeesIds", nullable = true) - open var groupAttendeesIds: String? = null + @get:Column(name = "groupAttendeeIds") + open var groupAttendeeIds: String? = null - @get:Column(name = "full_access_group_ids", length = 4000, nullable = true) + @get:Column(name = "full_access_group_ids", length = 4000) open var fullAccessGroupIds: String? = null - @get:Column(name = "full_access_user_ids", length = 4000, nullable = true) + @get:Column(name = "full_access_user_ids", length = 4000) open var fullAccessUserIds: String? = null @PropertyInfo(i18nKey = "poll.inputFields") - @get:Column(name = "inputFields", nullable = true, length = 1000) + @get:Column(name = "inputFields", length = 1000) open var inputFields: String? = null @PropertyInfo(i18nKey = "poll.state") @get:Column(name = "state", nullable = false) - open var state: State? = State.RUNNING + open var state: State = State.RUNNING @Transient - fun getPollAssignment(): PollAssignment { - val currentUserId = ThreadLocalUserContext.userId - val owner = if (owner != null) owner!!.id else null - return if (currentUserId == owner) { - PollAssignment.OWNER - } else { - PollAssignment.OTHER + fun getPollAssignment(): MutableList { + val currentUserId = ThreadLocalUserContext.userId!! + val assignmentList = mutableListOf() + if (currentUserId == this.owner?.id) { + assignmentList.add(PollAssignment.OWNER) + } + if (this.fullAccessUserIds != null) { + val accessUserIds = this.fullAccessUserIds!!.split(", ").map { it.toInt() }.toIntArray() + if (accessUserIds.contains(currentUserId)) { + assignmentList.add(PollAssignment.ACCESS) + } + } + if (this.attendeeIds != null) { + val attendeeUserIds = this.attendeeIds!!.split(", ").map { it.toInt() }.toIntArray() + if (attendeeUserIds.contains(currentUserId)) { + assignmentList.add(PollAssignment.ATTENDEE) + } } + if (assignmentList.isEmpty()) + assignmentList.add(PollAssignment.OTHER) + + return assignmentList } @Transient - fun getPollStatus(): PollStatus { - return if (LocalDate.now().isAfter(deadline)) { - PollStatus.FINISHED + fun getPollStatus(): PollState { + //TODO: Maybe change this to enum class State + + return if (this.state == State.FINISHED) { + PollState.FINISHED } else { - PollStatus.RUNNING + PollState.RUNNING } } diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index 324042f8f7..2297c78420 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -54,15 +54,15 @@ open class PollDao : BaseDao(PollDO::class.java){ return false } - fun isAttendee(obj: PollDO): Boolean { + private fun isAttendee(obj: PollDO): Boolean { val loggedInUser = user - val listOfAttendeesIds = ObjectMapper().readValue(obj.attendeesIds, IntArray::class.java) + val listOfAttendeesIds = ObjectMapper().readValue(obj.attendeeIds, IntArray::class.java) if (loggedInUser != null) { if(listOfAttendeesIds.contains(loggedInUser.id)){ return true } } - obj.attendeesIds= ObjectMapper().writeValueAsString(listOfAttendeesIds) + obj.attendeeIds= ObjectMapper().writeValueAsString(listOfAttendeesIds) return false diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatus.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollState.kt similarity index 79% rename from projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatus.kt rename to projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollState.kt index 1569af9d3d..6f752abfa2 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatus.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollState.kt @@ -2,7 +2,7 @@ package org.projectforge.business.poll import org.projectforge.common.i18n.I18nEnum -enum class PollStatus(val key: String): I18nEnum { +enum class PollState(val key: String): I18nEnum { RUNNING("running"), FINISHED("finished"); override val i18nKey: String diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatusFilter.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatusFilter.kt index 5a0a3052f4..468461d4d5 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatusFilter.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatusFilter.kt @@ -2,7 +2,7 @@ package org.projectforge.business.poll import org.projectforge.framework.persistence.api.impl.CustomResultFilter -class PollStatusFilter(val values: List): CustomResultFilter { +class PollStatusFilter(val values: List): CustomResultFilter { override fun match(list: MutableList, element: PollDO): Boolean { return values.contains(element.getPollStatus()) diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql index 3d6b1fd54d..6662ef9060 100644 --- a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql @@ -13,8 +13,8 @@ CREATE TABLE T_POLL deadline DATE NOT NULL, date DATE, state CHARACTER VARYING(1000) NOT NULL, - attendeesIds VARCHAR(5000), - groupAttendeesIds VARCHAR(5000), + attendeeIds VARCHAR(5000), + groupAttendeeIds VARCHAR(5000), full_access_user_ids CHARACTER VARYING(255), full_access_group_ids CHARACTER VARYING(255), inputFields CHARACTER Varying(100000) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index 2905fc4254..a66788f64f 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -27,16 +27,16 @@ class Poll( super.copyFrom(src) fullAccessGroups = Group.toGroupList(src.fullAccessGroupIds) fullAccessUsers = User.toUserList(src.fullAccessUserIds) - groupAttendees = Group.toGroupList(src.groupAttendeesIds) - attendees = User.toUserList(src.attendeesIds) + groupAttendees = Group.toGroupList(src.groupAttendeeIds) + attendees = User.toUserList(src.attendeeIds) } override fun copyTo(dest: PollDO) { super.copyTo(dest) dest.fullAccessGroupIds = Group.toIntList(fullAccessGroups) dest.fullAccessUserIds = User.toIntList(fullAccessUsers) - dest.groupAttendeesIds = Group.toIntList(groupAttendees) - dest.attendeesIds = User.toIntList(attendees) + dest.groupAttendeeIds = Group.toIntList(groupAttendees) + dest.attendeeIds = User.toIntList(attendees) } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index d06a18aa5e..ec97b18e7a 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -6,18 +6,11 @@ import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao import org.projectforge.business.user.service.UserService import org.projectforge.business.poll.* -import org.projectforge.business.vacation.model.VacationDO -import org.projectforge.business.vacation.model.VacationMode -import org.projectforge.business.vacation.model.VacationModeFilter -import org.projectforge.business.vacation.model.VacationStatus import org.projectforge.framework.i18n.translate import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.persistence.api.QueryFilter import org.projectforge.framework.persistence.api.impl.CustomResultFilter -import org.projectforge.menu.MenuItem -import org.projectforge.menu.MenuItemTargetType -import org.projectforge.rest.VacationExportPageRest import org.projectforge.rest.config.Rest import org.projectforge.rest.config.RestUtils import org.projectforge.rest.core.* @@ -212,11 +205,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun addMagicFilterElements(elements: MutableList) { elements.add( UIFilterListElement("assignment", label = translate("poll.pollAssignment"), defaultFilter = true) - .buildValues(PollAssignment.OWNER, PollAssignment.OTHER) + .buildValues(PollAssignment.OWNER, PollAssignment.ACCESS, PollAssignment.ATTENDEE, PollAssignment.OTHER) ) elements.add( UIFilterListElement("status", label = translate("poll.status"), defaultFilter = true) - .buildValues(PollStatus.RUNNING, PollStatus.FINISHED) + .buildValues(PollState.RUNNING, PollState.FINISHED) ) } @@ -236,7 +229,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. statusFilterEntry.synthetic = true val values = statusFilterEntry.value.values if (!values.isNullOrEmpty()) { - val enums = values.map { PollStatus.valueOf(it) } + val enums = values.map { PollState.valueOf(it) } filters.add(PollStatusFilter(enums)) } } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index b583cc52f6..1aabe2895b 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -74,7 +74,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { if (field.type == BaseType.TextQuestion) { col.add(UITextArea("responses[$index].answers[0]")) } - if (field.type == BaseType.YesNoQuestion) { + if (field.type == BaseType.SingleResponseQuestion) { col.add( UIRadioButton( "responses[$index].answers[0]", From 27308e28fa0491bcddc40447883d3b16dea6a8c9 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 21 Apr 2023 16:02:16 +0200 Subject: [PATCH 067/160] write questons but ansers not --- .../src/main/resources/application.properties | 4 +-- .../org/projectforge/rest/poll/CronJobs.kt | 3 +- .../rest/poll/Exel/ExcelExport.kt | 24 ++++++++++------ .../kotlin/org/projectforge/rest/poll/Poll.kt | 8 ++++++ .../projectforge/rest/poll/PollPageRest.kt | 28 +++++++++++-------- 5 files changed, 45 insertions(+), 22 deletions(-) diff --git a/projectforge-business/src/main/resources/application.properties b/projectforge-business/src/main/resources/application.properties index 782c9785d0..3dfed6b30d 100644 --- a/projectforge-business/src/main/resources/application.properties +++ b/projectforge-business/src/main/resources/application.properties @@ -247,9 +247,9 @@ mail.session.pfmailsession.standardEmailSender=sender@yourserver.org #Mail protocol: Plain, StartTLS,SSL mail.session.pfmailsession.encryption=Plain #Hostname of the email server -mail.session.pfmailsession.smtp.host=mail.yourserver.org +mail.session.pfmailsession.smtp.host=localhost #Port number of the email server -mail.session.pfmailsession.smtp.port=25 +mail.session.pfmailsession.smtp.port=1025 #The email server needs authentification mail.session.pfmailsession.smtp.auth=false #Authentification by user name diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt index f206c513d4..099226a53d 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt @@ -22,6 +22,7 @@ class CronJobs { private val log: Logger = LoggerFactory.getLogger(CronJobs::class.java) @Autowired private lateinit var pollDao: PollDao + @Autowired private lateinit var pollMailService: PollMailService @@ -109,7 +110,7 @@ class CronJobs { /** * Cron job for daily stuff */ - @Scheduled(cron = "0 0 1 * * *") // 1am everyday + @Scheduled(cron = "0 * * * * *") // 1am everyday fun dailyCronJobs() { cronDeletePolls() cronEndPolls() diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt index 3efaf8fa68..78497ef7a0 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt @@ -3,7 +3,7 @@ package org.projectforge.rest.poll.Exel import de.micromata.merlin.excel.ExcelRow import de.micromata.merlin.excel.ExcelSheet import de.micromata.merlin.excel.ExcelWorkbook -import org.projectforge.business.poll.PollResponseDO +import org.projectforge.business.poll.PollDao import org.projectforge.business.poll.PollResponseDao import org.projectforge.rest.dto.User import org.projectforge.rest.poll.Poll @@ -12,30 +12,37 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.core.io.ClassPathResource +import org.springframework.stereotype.Service import java.io.IOException -import java.time.LocalDate import java.util.* - +@Service class ExcelExport { private val log: Logger = LoggerFactory.getLogger(ExcelExport::class.java) private val FIRST_DATA_ROW_NUM = 1 + @Autowired - lateinit var pollResponseDao: PollResponseDao + private lateinit var pollResponseDao: PollResponseDao + @Autowired + private lateinit var pollDao: PollDao + fun getExcel(poll: Poll): ByteArray? { - var responses = pollResponseDao.internalLoadAll().filter { it.poll?.id == poll.id } + val responses = pollResponseDao.internalLoadAll().filter { it.poll?.id == poll.id } val classPathResource = ClassPathResource("officeTemplates/PollResultTemplate" + ".xlsx") - try { ExcelWorkbook(classPathResource.inputStream, classPathResource.file.name).use { workbook -> val excelSheet = workbook.getSheet(0) - val emptyRow = excelSheet.getRow(5) - val anzNewRows = poll.attendees!!.size + excelSheet.autosize(0) + val emptyRow = excelSheet.getRow(0) + var anzNewRows = 1 + + anzNewRows += (poll.attendees?.size ?: 0) + createNewRow(excelSheet, emptyRow, anzNewRows) setFirstRow(excelSheet, poll) poll.attendees?.sortedBy { it.displayName } @@ -44,6 +51,7 @@ class ExcelExport { responses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } setNewRows(excelSheet,poll, user, res, index) } + return returnByteFile(excelSheet) } } catch (e: NullPointerException) { diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index 2905fc4254..15a8f96355 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -1,5 +1,6 @@ package org.projectforge.rest.poll +import com.fasterxml.jackson.databind.ObjectMapper import org.projectforge.business.poll.PollDO import org.projectforge.framework.persistence.user.entities.PFUserDO import org.projectforge.rest.dto.BaseDTO @@ -29,6 +30,10 @@ class Poll( fullAccessUsers = User.toUserList(src.fullAccessUserIds) groupAttendees = Group.toGroupList(src.groupAttendeesIds) attendees = User.toUserList(src.attendeesIds) + if (src.inputFields != null) { + val fields = ObjectMapper().readValue(src.inputFields, MutableList::class.java) + inputFields = fields.map { Question().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() + } } override fun copyTo(dest: PollDO) { @@ -37,6 +42,9 @@ class Poll( dest.fullAccessUserIds = User.toIntList(fullAccessUsers) dest.groupAttendeesIds = Group.toIntList(groupAttendees) dest.attendeesIds = User.toIntList(attendees) + if(inputFields!= null){ + dest.inputFields = ObjectMapper().writeValueAsString(inputFields) + } } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 460d327a93..290f3c9718 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.projectforge.business.group.service.GroupService import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao +import org.projectforge.business.poll.PollResponseDao import org.projectforge.business.user.service.UserService import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext @@ -22,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.core.io.Resource import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* +import java.time.LocalDateTime import java.util.* import javax.servlet.http.HttpServletRequest @@ -39,9 +41,16 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @Autowired private lateinit var pollMailService: PollMailService + @Autowired private lateinit var pollDao: PollDao + @Autowired + private lateinit var pollResponseDao: PollResponseDao + + @Autowired + private lateinit var excelExport: ExcelExport + override fun newBaseDTO(request: HttpServletRequest?): Poll { val result = Poll() result.owner = ThreadLocalUserContext.user @@ -51,9 +60,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun transformForDB(dto: Poll): PollDO { val pollDO = PollDO() dto.copyTo(pollDO) - if(dto.inputFields!= null){ - pollDO.inputFields = ObjectMapper().writeValueAsString(dto.inputFields) - } return pollDO } @@ -62,10 +68,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun transformFromDB(pollDO: PollDO, editMode: Boolean): Poll { val poll = Poll() poll.copyFrom(pollDO) - if (pollDO.inputFields != null) { - val fields = ObjectMapper().readValue(pollDO.inputFields, MutableList::class.java) - poll.inputFields = fields.map { Question().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() - } User.restoreDisplayNames(poll.fullAccessUsers, userService) Group.restoreDisplayNames(poll.fullAccessGroups, groupService) User.restoreDisplayNames(poll.attendees, userService) @@ -99,6 +101,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. responseAction = ResponseAction(PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "${dto.id}", targetType = TargetType.REDIRECT), title = "poll.response.poll" )) + .add(UIButton.createExportButton( + id = "export-poll-response-button", + responseAction = ResponseAction("${Rest.URL}/poll/export/${dto.id}", targetType = TargetType.POST), + title = "poll.export.response.poll" + )) .add(lc, "title", "description", "location") .add(lc, "owner") .add(lc, "deadline", "date") @@ -362,13 +369,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @PostMapping("/export/{id}") fun export(request: HttpServletRequest ,@PathVariable("id") id: String) : ResponseEntity? { - val ihkExporter = ExcelExport() val poll = Poll() - var pollDo = pollDao.getById(id.toInt()) + val pollDo = pollDao.getById(id.toInt()) poll.copyFrom(pollDo) - val bytes: ByteArray? = ihkExporter + val bytes: ByteArray? = excelExport .getExcel(poll) - val filename = ("test.xlsx") + val filename = ( poll.title+ "_" + LocalDateTime.now().year +"_Result"+ ".xlsx") if (bytes == null || bytes.size == 0) { log.error("Oops, xlsx has zero size. Filename: $filename") From ccac935a91850b9b453ad9744a4a54b112ba6d27 Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Mon, 24 Apr 2023 09:41:39 +0200 Subject: [PATCH 068/160] i18n --- .../src/main/resources/I18nResources.properties | 1 + .../main/kotlin/org/projectforge/rest/poll/PollPageRest.kt | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index 50ae10de50..c7a3345bb5 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -1982,6 +1982,7 @@ poll.deadline = Deadline poll.date = Date poll.attendees = Attendees poll.group_attendees = Attendee Groups +poll.response.poll = Poll Awnser Page projectmanagement.personDays=Person days projectmanagement.personDays.short=pd question.deleteQuestion=Do your really want to delete this object finally? diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 52a44a5114..d79673078a 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -91,12 +91,13 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val layout = super.createEditLayout(dto, userAccess) val fieldset = UIFieldset(UILength(12)) - fieldset - .add(UIButton.createDefaultButton( + if(dto.id != null){ + fieldset.add(UIButton.createDefaultButton( id = "response-poll-button", responseAction = ResponseAction(PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "${dto.id}", targetType = TargetType.REDIRECT), title = "poll.response.poll" - )) + ))} + fieldset .add(lc, "title", "description", "location") .add(lc, "owner") .add(lc, "deadline", "date") From e87e83da0e5082a6199c56ec628c198c65bfde25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 25 Apr 2023 14:03:53 +0200 Subject: [PATCH 069/160] Added Errormessage to prevent access to finished poll --- .../org/projectforge/rest/poll/CronJobs.kt | 29 +++++++++---------- .../projectforge/rest/poll/PollPageRest.kt | 18 ++++++++---- .../rest/poll/ResponsePageRest.kt | 10 +++++++ 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt index f206c513d4..3067feb52d 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt @@ -25,6 +25,18 @@ class CronJobs { @Autowired private lateinit var pollMailService: PollMailService + + /** + * Cron job for daily stuff + */ +// @Scheduled(cron = "0 0 1 * * *") // 1am everyday + @Scheduled(cron = "0 * * * * *") // 1am everyday + fun dailyCronJobs() { + cronDeletePolls() + cronEndPolls() + } + + /** * Method to end polls after deadline */ @@ -52,14 +64,12 @@ class CronJobs { override fun getFilename(): String { return it.title+ "_" + LocalDateTime.now().year +"_Result"+ ".xlsx" } - override fun getContent(): ByteArray? { return exel } } list.add(attachment) - header = "Umfrage ist abgelaufen" mail =""" Die Umfrage ist zu ende. Hier die ergebnisse. @@ -95,8 +105,7 @@ class CronJobs { } - - if(mail.isNotEmpty()){ + if (mail.isNotEmpty()) { pollMailService.sendMail(to="test", subject = header, content = mail, mailAttachments = list) } } @@ -106,21 +115,11 @@ class CronJobs { } - /** - * Cron job for daily stuff - */ - @Scheduled(cron = "0 0 1 * * *") // 1am everyday - fun dailyCronJobs() { - cronDeletePolls() - cronEndPolls() - } - - /** * Method to delete old polls */ fun cronDeletePolls() { - // check if poll end in Future + // check if poll end in future val polls = pollDao.internalLoadAll() val pollsMoreThanOneYearPast = polls.filter { it.created?.before(Date.from(LocalDate.now().minusYears(1).atStartOfDay( ZoneId.systemDefault() diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index ed8f05ac20..1dcbaff6a5 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -91,12 +91,20 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val layout = super.createEditLayout(dto, userAccess) val fieldset = UIFieldset(UILength(12)) + if (dto.state == PollDO.State.RUNNING) { + fieldset + .add( + UIButton.createDefaultButton( + id = "response-poll-button", + responseAction = ResponseAction( + PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "${dto.id}", + targetType = TargetType.REDIRECT + ), + title = "poll.response.poll" + ) + ) + } fieldset - .add(UIButton.createDefaultButton( - id = "response-poll-button", - responseAction = ResponseAction(PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "${dto.id}", targetType = TargetType.REDIRECT), - title = "poll.response.poll" - )) .add(lc, "title", "description", "location") .add(lc, "owner") .add(lc, "deadline", "date") diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index fe682c41bc..4d05f46e3c 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -5,6 +5,9 @@ import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao import org.projectforge.business.poll.PollResponseDO import org.projectforge.business.poll.PollResponseDao +import org.projectforge.framework.access.AccessChecker +import org.projectforge.framework.access.AccessCheckerImpl.I18N_KEY_VIOLATION_USER_NOT_MEMBER_OF +import org.projectforge.framework.access.AccessException import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.utils.NumberHelper import org.projectforge.rest.config.Rest @@ -31,12 +34,19 @@ class ResponsePageRest : AbstractDynamicPageRest() { @Autowired private lateinit var pollResponseDao: PollResponseDao + @Autowired + private lateinit var accesscheck: AccessChecker + @GetMapping("dynamic") fun getForm(request: HttpServletRequest, @RequestParam("id") pollStringId: String?): FormLayoutData { val id = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") val pollData = pollDao.internalGetById(id) ?: PollDO() val pollDto = transformPollFromDB(pollData) + if (pollDto.state == PollDO.State.FINISHED) { + throw AccessException(I18N_KEY_VIOLATION_USER_NOT_MEMBER_OF, "Umfrage wurde bereits beendet"); + } + val layout = UILayout("poll.response.title") val fieldSet = UIFieldset(12, title = pollDto.title) fieldSet From 1c5ac1b27cfbe473c4d75489edb109555711c10c Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 26 Apr 2023 10:21:07 +0200 Subject: [PATCH 070/160] Autowired excelExport --- .../main/kotlin/org/projectforge/rest/poll/CronJobs.kt | 8 ++++---- .../kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt index 099226a53d..4afac1c2cf 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt @@ -26,6 +26,9 @@ class CronJobs { @Autowired private lateinit var pollMailService: PollMailService + @Autowired + private lateinit var exporter: ExcelExport + /** * Method to end polls after deadline */ @@ -39,14 +42,11 @@ class CronJobs { polls.forEach { if (it.deadline?.isBefore(LocalDate.now().minusDays(1)) == true) { it.state = PollDO.State.FINISHED - // check if state is open or closed - - val ihkExporter = ExcelExport() val poll = Poll() poll.copyFrom(it) - val exel = ihkExporter + val exel = exporter .getExcel(poll) val attachment = object : MailAttachment { diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt index 78497ef7a0..62d2a7295e 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt @@ -79,6 +79,10 @@ class ExcelExport { poll.inputFields?.forEachIndexed{i, question -> val answer = res?.responses?.find { it.uid == question.uid } + val cell = + answer?.answers?.forEachIndexed { j, s -> + + } excelRow.getCell(i+1).setCellValue(answer?.answers.toString()) } From 49efe035f1def574d950d79a63f6543f44bdd65b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Wed, 26 Apr 2023 11:54:43 +0200 Subject: [PATCH 071/160] Added check if a question was adde --- .../main/resources/I18nResources.properties | 23 ++++++++++--------- .../projectforge/rest/poll/PollPageRest.kt | 13 +++++++++-- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index c7a3345bb5..18c0e8bbfc 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -1973,16 +1973,17 @@ plugins.teamcal.title.add=Add calendar plugins.teamcal.title.edit=Edit team calendar plugins.teamcal.title.heading=Calendar plugins.teamcal.title.list=List of calendars -poll = Poll -poll.title = Title -poll.description = Description -poll.owner = Owner -poll.location = Location -poll.deadline = Deadline -poll.date = Date -poll.attendees = Attendees -poll.group_attendees = Attendee Groups -poll.response.poll = Poll Awnser Page +poll=Poll +poll.title=Title +poll.description=Description +poll.owner=Owner +poll.location=Location +poll.deadline=Deadline +poll.date=Date +poll.attendees=Attendees +poll.group_attendees=Attendee Groups +poll.response.poll=Poll Answer Page +poll.error.oneQuestionRequired=At least one question is required. projectmanagement.personDays=Person days projectmanagement.personDays.short=pd question.deleteQuestion=Do your really want to delete this object finally? @@ -1994,7 +1995,7 @@ scripting.download.filename.additional=Download only available for a few minutes scripting.download.filename.info=File generated by last script run. scripting.myScript.list=My scripts scripting.script=Script -scripting.script.availableVariables=Availabe variables +scripting.script.availableVariables=Available variables scripting.script.description.tooltip=Markdown format is supported. scripting.script.downloadBackups=Download backups scripting.script.downloadEffectiveScript=Download effective script diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 74f911230c..52485e7a98 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -5,6 +5,7 @@ import org.projectforge.business.group.service.GroupService import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao import org.projectforge.business.user.service.UserService +import org.projectforge.framework.access.AccessException import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.rest.config.Rest @@ -82,7 +83,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. userAccess = userAccess, ) .add(lc, "title", "description", "location", "owner", "deadline", "date", "state") - } @@ -165,6 +165,16 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return processedLayout } + + override fun onBeforeSaveOrUpdate(request: HttpServletRequest, obj: PollDO, postData: PostData) { + if (obj.inputFields.isNullOrEmpty() || obj.inputFields.equals("[]")) { + throw AccessException("poll.error.oneQuestionRequired") + } + + super.onBeforeSaveOrUpdate(request, obj, postData) + } + + override fun onAfterSaveOrUpdate(request: HttpServletRequest, poll: PollDO, postData: PostData) { super.onAfterSaveOrUpdate(request, poll, postData) pollMailService.sendMail(subject = "", content = "", to = "test.mail") @@ -189,7 +199,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } - // PostMapping add @PostMapping("/add") fun addQuestionField( From 6ebb9132af662f9c213da5d6369ba66b13526854 Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Fri, 28 Apr 2023 16:29:05 +0200 Subject: [PATCH 072/160] =?UTF-8?q?abstimmen=20f=C3=BCr=20andere=20m=C3=B6?= =?UTF-8?q?glich?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/projectforge/business/poll/PollDao.kt | 18 +++---- .../kotlin/org/projectforge/rest/poll/Poll.kt | 3 +- .../projectforge/rest/poll/PollPageRest.kt | 53 +++++++++++-------- .../rest/poll/ResponsePageRest.kt | 45 +++++++++++----- 4 files changed, 71 insertions(+), 48 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index 324042f8f7..ae4ab79631 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.projectforge.business.group.service.GroupService import org.projectforge.framework.access.OperationType import org.projectforge.framework.persistence.api.BaseDao +import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext.user import org.projectforge.framework.persistence.user.entities.PFUserDO import org.springframework.beans.factory.annotation.Autowired @@ -31,7 +32,7 @@ open class PollDao : BaseDao(PollDO::class.java){ return true }; if (obj != null && operationType == OperationType.SELECT){ - if(hasFullAccess(obj) || isAttendee(obj)) + if(hasFullAccess(obj) || isAttendee(obj, ThreadLocalUserContext.user?.id!!)) return true } if(obj != null) { @@ -54,17 +55,10 @@ open class PollDao : BaseDao(PollDO::class.java){ return false } - fun isAttendee(obj: PollDO): Boolean { - val loggedInUser = user - val listOfAttendeesIds = ObjectMapper().readValue(obj.attendeesIds, IntArray::class.java) - if (loggedInUser != null) { - if(listOfAttendeesIds.contains(loggedInUser.id)){ - return true - } - } - obj.attendeesIds= ObjectMapper().writeValueAsString(listOfAttendeesIds) - - + fun isAttendee(obj: PollDO, user: Int?): Boolean { + if (!obj.attendeesIds.isNullOrBlank() && obj.attendeesIds!!.split(", ").contains(user.toString()) + ) + return true return false } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index 2905fc4254..8996f0cc05 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -21,7 +21,8 @@ class Poll( var fullAccessGroups: List? = null, var fullAccessUsers: List? = null, var groupAttendees: List? = null, - var attendees: List? = null + var attendees: List? = null, + var delegationUser: User? = null ) : BaseDTO() { override fun copyFrom(src: PollDO) { super.copyFrom(src) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 74f911230c..77a16b2d4a 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -4,9 +4,11 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.projectforge.business.group.service.GroupService import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao +import org.projectforge.business.poll.PollResponseDao import org.projectforge.business.user.service.UserService import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext +import org.projectforge.framework.persistence.user.entities.PFUserDO import org.projectforge.rest.config.Rest import org.projectforge.rest.config.RestUtils import org.projectforge.rest.core.* @@ -89,14 +91,16 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { val lc = LayoutContext(PollDO::class.java) val layout = super.createEditLayout(dto, userAccess) - val fieldset = UIFieldset(UILength(12)) if(dto.id != null){ - fieldset.add(UIButton.createDefaultButton( - id = "response-poll-button", - responseAction = ResponseAction(PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "${dto.id}", targetType = TargetType.REDIRECT), - title = "poll.response.poll" - ))} + fieldset.add(UIRow() + .add(UIButton.createDefaultButton( + id = "response-poll-button", + responseAction = ResponseAction(PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "?pollid=${dto.id}&questionOwner=${dto.delegationUser?.id}", targetType = TargetType.REDIRECT), + title = "poll.response.poll" + )) + .add(UIInput(id = "delegationUser", label = "poll.delegation_label", dataType = UIDataType.USER)) + )} fieldset .add(lc, "title", "description", "location") .add(lc, "owner") @@ -156,6 +160,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. addQuestionFieldset(layout, dto) + layout.watchFields.add("delegationUser") layout.watchFields.addAll(listOf("groupAttendees")) var processedLayout = LayoutUtils.processEditPage(layout, dto, this) @@ -218,23 +223,29 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. watchFieldsTriggered: Array? ): ResponseEntity { val userAccess = UILayout.UserAccess() - val groupIds = dto.groupAttendees?.filter{it.id != null}?.map{it.id!!}?.toIntArray() - val userIds = UserService().getUserIds(groupService.getGroupUsers(groupIds)) - val users = User.toUserList(userIds) - User.restoreDisplayNames(users, userService) - val allUsers = dto.attendees?.toMutableList()?: mutableListOf() - - var counter = 0 ; - users?.forEach { user -> - if(allUsers?.filter { it.id == user.id }?.isEmpty() == true) { - allUsers.add(user) - counter ++ + if(watchFieldsTriggered?.get(0) == "groupAttendees") { + val groupIds = dto.groupAttendees?.filter { it.id != null }?.map { it.id!! }?.toIntArray() + val userIds = UserService().getUserIds(groupService.getGroupUsers(groupIds)) + val users = User.toUserList(userIds) + User.restoreDisplayNames(users, userService) + val allUsers = dto.attendees?.toMutableList() ?: mutableListOf() + + var counter = 0; + users?.forEach { user -> + if (allUsers?.filter { it.id == user.id }?.isEmpty() == true) { + allUsers.add(user) + counter++ + } } - } - - dto.groupAttendees = mutableListOf() - dto.attendees = allUsers.sortedBy { it.displayName } + dto.groupAttendees = mutableListOf() + dto.attendees = allUsers.sortedBy { it.displayName } + } + //i dont know why this is necessary + if (watchFieldsTriggered?.get(0) == "delegationUser"){ + dto.delegationUser = dto.delegationUser + } + dto.owner = userService.getUser(dto.owner?.id) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE ) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index fe682c41bc..03713563c9 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -5,7 +5,9 @@ import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao import org.projectforge.business.poll.PollResponseDO import org.projectforge.business.poll.PollResponseDao +import org.projectforge.business.user.service.UserService import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext +import org.projectforge.framework.persistence.user.entities.PFUserDO import org.projectforge.framework.utils.NumberHelper import org.projectforge.rest.config.Rest import org.projectforge.rest.core.AbstractDynamicPageRest @@ -20,6 +22,8 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import java.util.UUID import javax.servlet.http.HttpServletRequest +import org.projectforge.rest.dto.User + @RestController @RequestMapping("${Rest.URL}/response") @@ -31,10 +35,24 @@ class ResponsePageRest : AbstractDynamicPageRest() { @Autowired private lateinit var pollResponseDao: PollResponseDao + @Autowired + private lateinit var userService: UserService + @GetMapping("dynamic") - fun getForm(request: HttpServletRequest, @RequestParam("id") pollStringId: String?): FormLayoutData { + fun getForm(request: HttpServletRequest, @RequestParam("pollid") pollStringId: String?, @RequestParam("questionOwner") delUser: String?): FormLayoutData { val id = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") + + //used to load awnsers, is an attendee chosen by a fullaccessuser in order to awnser for them or the Threadlocal User + var questionOwner: Int? = null + val pollData = pollDao.internalGetById(id) ?: PollDO() + + if (delUser != "null" && pollDao.hasFullAccess(pollData) && pollDao.isAttendee(pollData, delUser?.toInt())) + questionOwner = delUser?.toInt() + else + questionOwner = ThreadLocalUserContext.user?.id + + val pollDto = transformPollFromDB(pollData) val layout = UILayout("poll.response.title") @@ -43,7 +61,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { .add(UIReadOnlyField(value = pollDto.description, label = "Description")) .add(UIReadOnlyField(value = pollDto.location, label = "Location")) .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = "Owner")) - .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = "Deadline")) + .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = "Deadline")) layout.add(fieldSet) @@ -51,7 +69,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { pollResponse.poll = pollData pollResponseDao.internalLoadAll().firstOrNull { response -> - response.owner == ThreadLocalUserContext.user + response.owner?.id == questionOwner && response.poll?.id == pollData.id }?.let { pollResponse.copyFrom(it) @@ -101,13 +119,13 @@ class ResponsePageRest : AbstractDynamicPageRest() { layout.add( UIButton.createDefaultButton( - id = "doResponse", - title = "response", + id = "addResponse", + title = "submit", responseAction = ResponseAction( RestResolver.getRestUrl( this::class.java, - "doResponse" - ), targetType = TargetType.POST + "addResponse" + ) + "/?questionOwner=${questionOwner}", targetType = TargetType.POST ) ) ) @@ -115,18 +133,17 @@ class ResponsePageRest : AbstractDynamicPageRest() { return FormLayoutData(pollResponse, layout, createServerData(request)) } - @PostMapping("doResponse") - fun doResponse( + @PostMapping("addResponse") + fun addResponse( request: HttpServletRequest, - @RequestBody postData: PostData + @RequestBody postData: PostData, @RequestParam("questionOwner") questionOwner: Int? ): ResponseEntity? { - + val questionOwner: Int? = questionOwner val pollResponseDO = PollResponseDO() postData.data.copyTo(pollResponseDO) - pollResponseDO.owner = ThreadLocalUserContext.user - + pollResponseDO.owner = userService.getUser(questionOwner) pollResponseDao.internalLoadAll().firstOrNull { pollResponse -> - pollResponse.owner == ThreadLocalUserContext.user + pollResponse.owner?.id == questionOwner && pollResponse.poll?.id == postData.data.poll?.id }?.let { it.responses = pollResponseDO.responses From a4d20768a77c74792b7b3f8dc772fc79ae64830c Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 5 May 2023 17:29:37 +0200 Subject: [PATCH 073/160] make excel better --- .../rest/poll/Exel/ExcelExport.kt | 70 +++++++++++++++---- .../projectforge/rest/poll/PollMailService.kt | 12 ++-- .../projectforge/rest/poll/PollPageRest.kt | 5 +- .../rest/poll/ResponsePageRest.kt | 18 +---- 4 files changed, 65 insertions(+), 40 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt index 62d2a7295e..347cd1a3d2 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt @@ -3,11 +3,16 @@ package org.projectforge.rest.poll.Exel import de.micromata.merlin.excel.ExcelRow import de.micromata.merlin.excel.ExcelSheet import de.micromata.merlin.excel.ExcelWorkbook +import org.apache.poi.ss.usermodel.CellStyle +import org.apache.poi.ss.usermodel.Font +import org.apache.poi.ss.usermodel.HorizontalAlignment +import org.apache.poi.ss.util.CellRangeAddress import org.projectforge.business.poll.PollDao import org.projectforge.business.poll.PollResponseDao import org.projectforge.rest.dto.User import org.projectforge.rest.poll.Poll import org.projectforge.rest.poll.PollResponse +import org.projectforge.rest.poll.types.BaseType import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired @@ -21,7 +26,7 @@ class ExcelExport { private val log: Logger = LoggerFactory.getLogger(ExcelExport::class.java) - private val FIRST_DATA_ROW_NUM = 1 + private val FIRST_DATA_ROW_NUM = 2 @Autowired private lateinit var pollResponseDao: PollResponseDao @@ -37,14 +42,15 @@ class ExcelExport { try { ExcelWorkbook(classPathResource.inputStream, classPathResource.file.name).use { workbook -> val excelSheet = workbook.getSheet(0) + val style = workbook.createOrGetCellStyle() excelSheet.autosize(0) val emptyRow = excelSheet.getRow(0) - var anzNewRows = 1 + var anzNewRows = 2 anzNewRows += (poll.attendees?.size ?: 0) createNewRow(excelSheet, emptyRow, anzNewRows) - setFirstRow(excelSheet, poll) + setFirstRow(excelSheet, style, poll) poll.attendees?.sortedBy { it.displayName } poll.attendees?.forEachIndexed { index, user -> val res = PollResponse() @@ -62,28 +68,62 @@ class ExcelExport { return null } - private fun setFirstRow(excelSheet: ExcelSheet, poll: Poll){ + private fun setFirstRow(excelSheet: ExcelSheet,style: CellStyle,poll: Poll){ val excelRow = excelSheet.getRow(0) - poll.inputFields?.forEachIndexed{i, question -> - excelRow.getCell(i+1).setCellValue(question.question) + val excelRow1 = excelSheet.getRow(1) + var counter = 0 + + style.alignment = HorizontalAlignment.CENTER + + var merge = 0 + poll.inputFields?.forEach{question -> + merge += 1 + if(question.type==BaseType.MultiResponseQuestion || + question.type==BaseType.SingleResponseQuestion || + question.type==BaseType.DateQuestion) { + question.answers?.forEach { answer -> + counter++ + excelRow1.getCell(counter).setCellValue(answer) + excelRow1.getCell(counter).setCellStyle(style) + excelSheet.autosize(counter) + } + excelRow.getCell(merge).setCellValue(question.question) + merge += question.answers?.size ?: 0 + //excelSheet.addMergeRegion(CellRangeAddress(0,0,merge,merge + counter)) + excelSheet.autosize(merge) + + } + else { + excelRow.getCell(merge).setCellValue(question.question) + excelRow.setCellStyle(style) + excelSheet.autosize(merge) + } } - excelRow.setHeight(20F) + excelRow.setHeight(30F) } private fun setNewRows(excelSheet: ExcelSheet, poll:Poll, user: User, res:PollResponse?, index: Int) { - - val excelRow = excelSheet.getRow(FIRST_DATA_ROW_NUM + index) - excelRow.getCell(0).setCellValue(user.displayName) + excelRow.getCell(0).setCellValue(user.displayName) + excelSheet.autosize(0) + var chell=0 poll.inputFields?.forEachIndexed{i, question -> - val answer = res?.responses?.find { it.uid == question.uid } - val cell = - answer?.answers?.forEachIndexed { j, s -> - + val answer = res?.responses?.find { it.questionUid == question.uid } + + answer?.answers?.forEachIndexed { ind, antwort -> + chell++ + if (question.type == BaseType.SingleResponseQuestion || question.type == BaseType.MultiResponseQuestion){ + if(antwort is Boolean && antwort == true){ + excelRow.getCell(chell).setCellValue("X") + } + } + else { + excelRow.getCell(chell).setCellValue(antwort.toString()) + } + excelSheet.autosize(chell) } - excelRow.getCell(i+1).setCellValue(answer?.answers.toString()) } excelRow.setHeight(20F) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt index 413d9e71dc..83b382e730 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt @@ -14,12 +14,12 @@ class PollMailService { fun sendMail(to:String ,subject: String,content: String, mailAttachments: List?= null){ - val mail = Mail() - mail.subject = subject - mail.contentType = Mail.CONTENTTYPE_HTML - mail.setTo(to) - mail.content = content - sendMail.send(mail, attachments = mailAttachments) + val mail = Mail() + mail.subject = subject + mail.contentType = Mail.CONTENTTYPE_HTML + mail.setTo(to) + mail.content = content + sendMail.send(mail, attachments = mailAttachments) } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 290f3c9718..76483a3a76 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -151,7 +151,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. addQuestionFieldset(layout, dto) - layout.watchFields.addAll(listOf("groupAttendees")) + layout.watchFields.addAll(listOf("groupAttendees")) return LayoutUtils.processEditPage(layout, dto, this) } @@ -309,7 +309,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. "${Rest.URL}/poll/deleteAnswer/${questionUid}/${answerIndex}", targetType = TargetType.POST ) ).withConfirmMessage(layout, confirmMessage = "Willst du wirklich diese Antwort löschen?")) - ) + ) return row } @@ -372,6 +372,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val poll = Poll() val pollDo = pollDao.getById(id.toInt()) poll.copyFrom(pollDo) + User.restoreDisplayNames(poll.attendees, userService) val bytes: ByteArray? = excelExport .getExcel(poll) val filename = ( poll.title+ "_" + LocalDateTime.now().year +"_Result"+ ".xlsx") diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 1aabe2895b..8168c40b0a 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -74,26 +74,10 @@ class ResponsePageRest : AbstractDynamicPageRest() { if (field.type == BaseType.TextQuestion) { col.add(UITextArea("responses[$index].answers[0]")) } - if (field.type == BaseType.SingleResponseQuestion) { - col.add( - UIRadioButton( - "responses[$index].answers[0]", - value = field.answers!![0], - label = field.answers?.get(0) ?: "" - ) - ) - col.add( - UIRadioButton( - "responses[$index].answers[0]", - value = field.answers!![1], - label = field.answers?.get(1) ?: "" - ) - ) - } if (field.type == BaseType.DateQuestion) { col.add(UITextArea("responses[$index].answers[0]")) } - if (field.type == BaseType.MultiResponseQuestion) { + if (field.type == BaseType.MultiResponseQuestion || field.type == BaseType.SingleResponseQuestion) { field.answers?.forEachIndexed { index2, _ -> col.add(UICheckbox("responses[$index].answers[$index2]", label = field.answers?.get(index2) ?: "")) } From cf0005ed78ea7574f5f0049a66b366336beb5b60 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 5 May 2023 17:50:24 +0200 Subject: [PATCH 074/160] merge develop into Excel --- .../rest/poll/PollInfoPageRest.kt | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt new file mode 100644 index 0000000000..3fbff669bb --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt @@ -0,0 +1,76 @@ +package org.projectforge.rest.poll + +import org.projectforge.rest.config.Rest +import org.projectforge.rest.core.AbstractDynamicPageRest +import org.projectforge.rest.core.PagesResolver +import org.projectforge.rest.dto.FormLayoutData +import org.projectforge.ui.* +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import javax.servlet.http.HttpServletRequest + + +@RestController +@RequestMapping("${Rest.URL}/pollInfo") +class PollInfoPageRest: AbstractDynamicPageRest() { + + @GetMapping("dynamic") + fun getForm(request: HttpServletRequest): FormLayoutData { + + val layout = UILayout("poll.infopage") + val field = UIFieldset() + .add(UILabel(""" Anleitung um eine Umfrage zu erstellen + | Als erstes werden die Parameter einer Umfrage angelegt. + """.trimMargin())) + + field.add(UICol() + .add(UIReadOnlyField("title", label = "title", value = "Test"))) + field.add(UICol() + .add(UIReadOnlyField("description", label = "description", value = "description"))) + field.add(UICol() + .add(UIReadOnlyField("location", label = "location", value = "location"))) + field.add(UICol() + .add(UIReadOnlyField("owner", label = "owner", value = "owner"))) + field.add(UICol() + .add(UIReadOnlyField("deadline", label = "deadline", value = "deadline"))) + + field.add(UIRow().add(UICol().add(UILabel("""Anschließend werden die Fragen der Umfrage angelegt. + Die Fragen können aus verschiedenen Typen bestehen. """)))) + + layout.add(field) + + layout.add(UIFieldset().add(UILabel("YesNoQuestion")).add( + UICol() + .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage die mit Ja oder Nein Beantwortet werden kann")) + )) + layout.add(UIFieldset().add(UILabel("MultipleChoiceQuestion")).add( + UICol() + .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage die mit mehreren Antworten Beantwortet werden kann")) + )) + layout.add(UIFieldset().add(UILabel("TextQuestion")).add( + UICol() + .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage die mit einer Freitext Antwort Beantwortet werden kann")) + + )) + layout.add(UIFieldset().add(UILabel("DateQuestion")).add( + UICol() + .add(UIReadOnlyField("question", label = "Question", + value = """Eine Frage ob an einem Tag (Uhrzeit) die Teilnehmer zeit haben. Die einem Ja, Nein oder Vielleicht + Beantwortet werden kann""" + )))) + layout.add(UIFieldset().add(UILabel("Dropdown")).add( + UICol() + .add(UIReadOnlyField("question", label = "Question", + value = """ Eine Frage die mit einem Dropdown Beantwortet werden kann""" + )))) + + + LayoutUtils.process(layout) + + return FormLayoutData(null, layout, createServerData(request)) + + + } + +} \ No newline at end of file From 66f9df44c3bd909e5700a8ff8159a54eafa4638c Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 5 May 2023 17:51:36 +0200 Subject: [PATCH 075/160] merge develop into Excel --- .../org/projectforge/business/poll/PollDO.kt | 9 +- .../business/poll/PollResponseDO.kt | 4 +- .../main/resources/I18nResources.properties | 10 ++ .../migrate/common/V7.5.1.2__7.5.1.0-POLL.sql | 12 +- .../org/projectforge/rest/poll/CronJobs.kt | 28 ++-- .../projectforge/rest/poll/PollPageRest.kt | 136 +++++++++--------- .../rest/poll/ResponsePageRest.kt | 29 +++- .../projectforge/rest/poll/types/Question.kt | 1 - 8 files changed, 135 insertions(+), 94 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 91d05af1fd..17f88704b2 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -1,6 +1,7 @@ package org.projectforge.business.poll import org.hibernate.search.annotations.Indexed +import org.hibernate.search.annotations.IndexedEmbedded import org.projectforge.business.common.BaseUserGroupRightsDO import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.persistence.api.AUserRightId @@ -28,8 +29,8 @@ open class PollDO : DefaultBaseDO() { @get:PropertyInfo(i18nKey = "poll.owner") @get:ManyToOne(fetch = FetchType.LAZY) - @get:JoinColumn(name = "owner_pk", nullable = false) - var owner: PFUserDO? = null + @get:JoinColumn(name = "owner_fk", nullable = false) + open var owner: PFUserDO? = null @PropertyInfo(i18nKey = "poll.location") @get:Column(name = "location") @@ -47,13 +48,15 @@ open class PollDO : DefaultBaseDO() { @get:Column(name = "attendeesIds", nullable = true) open var attendeesIds: String? = null - @PropertyInfo(i18nKey = "poll.group_attendees") + @PropertyInfo(i18nKey = "poll.attendee_groups") @get:Column(name = "groupAttendeesIds", nullable = true) open var groupAttendeesIds: String? = null + @PropertyInfo(i18nKey = "poll.full_access_groups") @get:Column(name = "full_access_group_ids", length = 4000, nullable = true) open var fullAccessGroupIds: String? = null + @PropertyInfo(i18nKey = "poll.full_access_user") @get:Column(name = "full_access_user_ids", length = 4000, nullable = true) open var fullAccessUserIds: String? = null diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt index cbb4d3aede..2775557ec7 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt @@ -17,12 +17,12 @@ open class PollResponseDO : DefaultBaseDO() { @get:PropertyInfo(i18nKey = "poll.response.poll") @get:ManyToOne(fetch = FetchType.LAZY) - @get:JoinColumn(name = "poll_pk", nullable = false) + @get:JoinColumn(name = "poll_fk", nullable = false) open var poll: PollDO? = null @get:PropertyInfo(i18nKey = "poll.response.owner") @get:ManyToOne(fetch = FetchType.LAZY) - @get:JoinColumn(name = "owner_pk", nullable = false) + @get:JoinColumn(name = "owner_fk", nullable = false) open var owner: PFUserDO? = null @PropertyInfo(i18nKey = "poll.responses") diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index ef297a0939..c7a3345bb5 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -1973,6 +1973,16 @@ plugins.teamcal.title.add=Add calendar plugins.teamcal.title.edit=Edit team calendar plugins.teamcal.title.heading=Calendar plugins.teamcal.title.list=List of calendars +poll = Poll +poll.title = Title +poll.description = Description +poll.owner = Owner +poll.location = Location +poll.deadline = Deadline +poll.date = Date +poll.attendees = Attendees +poll.group_attendees = Attendee Groups +poll.response.poll = Poll Awnser Page projectmanagement.personDays=Person days projectmanagement.personDays.short=pd question.deleteQuestion=Do your really want to delete this object finally? diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql index 3d6b1fd54d..c16b83fe96 100644 --- a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql @@ -9,7 +9,7 @@ CREATE TABLE T_POLL title CHARACTER VARYING(1000) NOT NULL, description CHARACTER VARYING(1000), location CHARACTER VARYING(1000), - owner_pk INTEGER NOT NULL, + owner_fk INTEGER NOT NULL, deadline DATE NOT NULL, date DATE, state CHARACTER VARYING(1000) NOT NULL, @@ -23,7 +23,7 @@ CREATE TABLE T_POLL ALTER TABLE T_POLL ADD CONSTRAINT t_poll_pkey PRIMARY KEY (pk); ALTER TABLE T_POLL - ADD CONSTRAINT fk_t_poll_pf_user FOREIGN KEY (owner_pk) REFERENCES t_pf_user (pk); + ADD CONSTRAINT fk_t_poll_pf_user FOREIGN KEY (owner_fk) REFERENCES t_pf_user (pk); CREATE TABLE T_POLL_RESPONSE ( @@ -31,14 +31,14 @@ CREATE TABLE T_POLL_RESPONSE deleted BOOLEAN NOT NULL, created TIMESTAMP WITHOUT TIME ZONE, last_update TIMESTAMP WITHOUT TIME ZONE, - poll_pk INTEGER NOT NULL, - owner_pk INTEGER NOT NULL, + poll_fk INTEGER NOT NULL, + owner_fk INTEGER NOT NULL, responses CHARACTER Varying(10000) ); ALTER TABLE T_POLL_RESPONSE ADD CONSTRAINT t_poll_response_pkey PRIMARY KEY (pk); ALTER TABLE T_POLL_RESPONSE - ADD CONSTRAINT fk_t_poll_response_pf_user FOREIGN KEY (owner_pk) REFERENCES t_pf_user (pk); + ADD CONSTRAINT fk_t_poll_response_pf_user FOREIGN KEY (owner_fk) REFERENCES t_pf_user (pk); ALTER TABLE T_POLL_RESPONSE - ADD CONSTRAINT fk_t_poll_response_poll FOREIGN KEY (poll_pk) REFERENCES t_poll (pk); \ No newline at end of file + ADD CONSTRAINT fk_t_poll_response_poll FOREIGN KEY (poll_fk) REFERENCES t_poll (pk); \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt index 4afac1c2cf..afdaa8ec58 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt @@ -29,6 +29,17 @@ class CronJobs { @Autowired private lateinit var exporter: ExcelExport + /** + * Cron job for daily stuff + */ +// @Scheduled(cron = "0 0 1 * * *") // 1am everyday + @Scheduled(cron = "0 * * * * *") // 1am everyday + fun dailyCronJobs() { + cronDeletePolls() + cronEndPolls() + } + + /** * Method to end polls after deadline */ @@ -53,14 +64,12 @@ class CronJobs { override fun getFilename(): String { return it.title+ "_" + LocalDateTime.now().year +"_Result"+ ".xlsx" } - override fun getContent(): ByteArray? { return exel } } list.add(attachment) - header = "Umfrage ist abgelaufen" mail =""" Die Umfrage ist zu ende. Hier die ergebnisse. @@ -96,8 +105,7 @@ class CronJobs { } - - if(mail.isNotEmpty()){ + if (mail.isNotEmpty()) { pollMailService.sendMail(to="test", subject = header, content = mail, mailAttachments = list) } } @@ -107,21 +115,11 @@ class CronJobs { } - /** - * Cron job for daily stuff - */ - @Scheduled(cron = "0 * * * * *") // 1am everyday - fun dailyCronJobs() { - cronDeletePolls() - cronEndPolls() - } - - /** * Method to delete old polls */ fun cronDeletePolls() { - // check if poll end in Future + // check if poll end in future val polls = pollDao.internalLoadAll() val pollsMoreThanOneYearPast = polls.filter { it.created?.before(Date.from(LocalDate.now().minusYears(1).atStartOfDay( ZoneId.systemDefault() diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 76483a3a76..adbe369296 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -8,6 +8,7 @@ import org.projectforge.business.poll.PollResponseDao import org.projectforge.business.user.service.UserService import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext +import org.projectforge.menu.MenuItem import org.projectforge.rest.config.Rest import org.projectforge.rest.config.RestUtils import org.projectforge.rest.core.* @@ -60,6 +61,9 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun transformForDB(dto: Poll): PollDO { val pollDO = PollDO() dto.copyTo(pollDO) + if (dto.inputFields != null) { + pollDO.inputFields = ObjectMapper().writeValueAsString(dto.inputFields) + } return pollDO } @@ -89,9 +93,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } - override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { - val lc = LayoutContext(PollDO::class.java) val layout = super.createEditLayout(dto, userAccess) val fieldset = UIFieldset(UILength(12)) @@ -101,11 +103,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. responseAction = ResponseAction(PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "${dto.id}", targetType = TargetType.REDIRECT), title = "poll.response.poll" )) - .add(UIButton.createExportButton( - id = "export-poll-response-button", - responseAction = ResponseAction("${Rest.URL}/poll/export/${dto.id}", targetType = TargetType.POST), - title = "poll.export.response.poll" - )) .add(lc, "title", "description", "location") .add(lc, "owner") .add(lc, "deadline", "date") @@ -113,49 +110,65 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. .add(UISelect.createGroupSelect(lc, "fullAccessGroups", true, "poll.fullAccessGroups")) .add(UISelect.createUserSelect(lc, "attendees", true, "poll.attendees")) .add(UISelect.createGroupSelect(lc, "groupAttendees", true, "poll.groupAttendees")) - .add( + if (dto.id == null) { + fieldset.add( UIRow() .add( UICol(UILength(xs = 9, sm = 9, md = 9, lg = 9)) - .add(UISelect("questionType", values = BaseType.values().map { UISelectValue(it, it.name) }, label = "questionType")) - ) - .add( - UICol(UILength(xs = 3, sm = 3, md = 3, lg = 3)) .add( - UIButton.createDefaultButton( - id = "add-question-button", - responseAction = ResponseAction("${Rest.URL}/poll/add", targetType = TargetType.POST), - title = "Eigene Frage hinzufügen" + UISelect( + "questionType", + values = BaseType.values().map { UISelectValue(it, it.name) }, + label = "questionType" ) ) ) - ) - .add( - UIRow() - .add( - UICol(UILength(xs = 9, sm = 9, md = 9, lg = 9)) - ) .add( UICol(UILength(xs = 3, sm = 3, md = 3, lg = 3)) .add( UIButton.createDefaultButton( - id = "micromata-vorlage-button", - responseAction = ResponseAction("${Rest.URL}/poll/addPremadeQuestions", targetType = TargetType.POST), - title = "Micromata Vorlage nutzen" + id = "add-question-button", + responseAction = ResponseAction( + "${Rest.URL}/poll/add", + targetType = TargetType.POST + ), + title = "Eigene Frage hinzufügen" ) ) ) ) - + .add( + UIRow() + .add( + UICol(UILength(xs = 9, sm = 9, md = 9, lg = 9)) + ) + .add( + UICol(UILength(xs = 3, sm = 3, md = 3, lg = 3)) + .add( + UIButton.createDefaultButton( + id = "micromata-vorlage-button", + responseAction = ResponseAction( + "${Rest.URL}/poll/addPremadeQuestions", + targetType = TargetType.POST + ), + title = "Micromata Vorlage nutzen" + ) + ) + ) + ) + } layout.add(fieldset) addQuestionFieldset(layout, dto) layout.watchFields.addAll(listOf("groupAttendees")) - return LayoutUtils.processEditPage(layout, dto, this) - } - //TODO refactor this whole file into multiple smaller files + var processedLayout = LayoutUtils.processEditPage(layout, dto, this) + processedLayout.actions.filterIsInstance().find { + it.id == "create" + }?.confirmMessage = "Willst du wirklich die Umfrage erstellen? Du kannst die Fragen im Nachhinein nicht mehr bearbeiten." + return processedLayout + } override fun onAfterSaveOrUpdate(request: HttpServletRequest, poll: PollDO, postData: PostData) { super.onAfterSaveOrUpdate(request, poll, postData) @@ -173,15 +186,13 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val found = dto.inputFields?.find { it.uid == fieldUid } found?.answers?.add("") - + dto.owner = userService.getUser(dto.owner?.id) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) } - - // PostMapping add @PostMapping("/add") fun addQuestionField( @@ -195,12 +206,9 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. if(type == BaseType.SingleResponseQuestion) { question.answers = mutableListOf("ja", "nein") } - if(type == BaseType.DateQuestion) { - question.answers = mutableListOf("Ja", "Vielleicht", "Nein") - } dto.inputFields!!.add(question) - + dto.owner = userService.getUser(dto.owner?.id) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) @@ -257,50 +265,44 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } private fun addQuestionFieldset(layout: UILayout, dto: Poll) { dto.inputFields?.forEachIndexed { index, field -> + val objGiven = dto.id != null //differentiates between initial creation and editing val fieldset = UIFieldset(UILength(12), title = field.type.toString()) - .add(generateDeleteButton(layout, field.uid)) - .add(UIInput("inputFields[${index}].question", label = "Frage")) + if(!objGiven){ + fieldset.add(generateDeleteButton(layout, field.uid)) + } + fieldset.add(getUiElement(objGiven, "inputFields[${index}].question", "Frage")) if (field.type == BaseType.SingleResponseQuestion || field.type == BaseType.MultiResponseQuestion) { - val groupLayout = UIGroup() field.answers?.forEachIndexed { answerIndex, _ -> - groupLayout.add(generateSingleAndMultiResponseAnswer(index, field.uid, answerIndex, layout)) + fieldset.add(generateSingleAndMultiResponseAnswer(objGiven, index, field.uid, answerIndex, layout)) } - groupLayout.add( - UIRow().add( - UIButton.createAddButton( - responseAction = ResponseAction( - "${Rest.URL}/poll/addAnswer/${field.uid}", targetType = TargetType.POST + if(!objGiven) { + fieldset.add( + UIRow().add( + UIButton.createAddButton( + responseAction = ResponseAction( + "${Rest.URL}/poll/addAnswer/${field.uid}", targetType = TargetType.POST + ) ) ) ) - ) - fieldset.add(groupLayout) - } - - if (field.type == BaseType.DateQuestion) { - fieldset - .add( - UIInput( - "inputFields[${index}].question", - label = "Hast du am ... Zeit?" - ) - ) + } } layout.add(fieldset) } } - private fun generateSingleAndMultiResponseAnswer(inputFieldIndex: Int, questionUid: String?, answerIndex: Int, layout: UILayout):UIRow { + private fun generateSingleAndMultiResponseAnswer(objGiven: Boolean, inputFieldIndex: Int, questionUid: String?, answerIndex: Int, layout: UILayout):UIRow { val row = UIRow() row.add( UICol() .add( - UIInput("inputFields[${inputFieldIndex}].answers[${answerIndex}]", label = "Answer ${answerIndex + 1}") + getUiElement(objGiven, "inputFields[${inputFieldIndex}].answers[${answerIndex}]", "Answer ${answerIndex + 1}") ) ) - .add( + if(!objGiven){ + row.add( UICol() .add( UIButton.createDangerButton( @@ -308,8 +310,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. responseAction = ResponseAction( "${Rest.URL}/poll/deleteAnswer/${questionUid}/${answerIndex}", targetType = TargetType.POST ) - ).withConfirmMessage(layout, confirmMessage = "Willst du wirklich diese Antwort löschen?")) - ) + ).withConfirmMessage(layout, confirmMessage = "Willst du wirklich diese Antwortmöglichkeit löschen?"))) + } return row } @@ -324,7 +326,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val userAccess = UILayout.UserAccess(insert = true, update = true) dto.inputFields?.find { it.uid.equals(questionUid) }?.answers?.removeAt(answerIndex) - + dto.owner = userService.getUser(dto.owner?.id) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) @@ -366,7 +368,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) } - @PostMapping("/export/{id}") fun export(request: HttpServletRequest ,@PathVariable("id") id: String) : ResponseEntity? { val poll = Poll() @@ -384,4 +385,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return RestUtils.downloadFile(filename, bytes) } + //once created, questions should be ReadOnly + private fun getUiElement(obj: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement{ + if (obj) + return UIReadOnlyField(id, label = label, dataType = dataType) + else + return UIInput(id, label = label, dataType = dataType) + } } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 8168c40b0a..4d05f46e3c 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -5,6 +5,9 @@ import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao import org.projectforge.business.poll.PollResponseDO import org.projectforge.business.poll.PollResponseDao +import org.projectforge.framework.access.AccessChecker +import org.projectforge.framework.access.AccessCheckerImpl.I18N_KEY_VIOLATION_USER_NOT_MEMBER_OF +import org.projectforge.framework.access.AccessException import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.utils.NumberHelper import org.projectforge.rest.config.Rest @@ -31,12 +34,19 @@ class ResponsePageRest : AbstractDynamicPageRest() { @Autowired private lateinit var pollResponseDao: PollResponseDao + @Autowired + private lateinit var accesscheck: AccessChecker + @GetMapping("dynamic") fun getForm(request: HttpServletRequest, @RequestParam("id") pollStringId: String?): FormLayoutData { val id = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") val pollData = pollDao.internalGetById(id) ?: PollDO() val pollDto = transformPollFromDB(pollData) + if (pollDto.state == PollDO.State.FINISHED) { + throw AccessException(I18N_KEY_VIOLATION_USER_NOT_MEMBER_OF, "Umfrage wurde bereits beendet"); + } + val layout = UILayout("poll.response.title") val fieldSet = UIFieldset(12, title = pollDto.title) fieldSet @@ -74,10 +84,23 @@ class ResponsePageRest : AbstractDynamicPageRest() { if (field.type == BaseType.TextQuestion) { col.add(UITextArea("responses[$index].answers[0]")) } - if (field.type == BaseType.DateQuestion) { - col.add(UITextArea("responses[$index].answers[0]")) + if (field.type == BaseType.SingleResponseQuestion) { + col.add( + UIRadioButton( + "responses[$index].answers[0]", + value = field.answers!![0], + label = field.answers?.get(0) ?: "" + ) + ) + col.add( + UIRadioButton( + "responses[$index].answers[0]", + value = field.answers!![1], + label = field.answers?.get(1) ?: "" + ) + ) } - if (field.type == BaseType.MultiResponseQuestion || field.type == BaseType.SingleResponseQuestion) { + if (field.type == BaseType.MultiResponseQuestion) { field.answers?.forEachIndexed { index2, _ -> col.add(UICheckbox("responses[$index].answers[$index2]", label = field.answers?.get(index2) ?: "")) } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt index 085fc28a86..185eb67171 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt @@ -24,5 +24,4 @@ enum class BaseType { TextQuestion, SingleResponseQuestion, MultiResponseQuestion, - DateQuestion } \ No newline at end of file From fb724b8b3c72df54c3d4337d6134591f0a745434 Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Mon, 8 May 2023 14:12:46 +0200 Subject: [PATCH 076/160] =?UTF-8?q?abstimmen=20f=C3=BCr=20andere=20m=C3=B6?= =?UTF-8?q?glich?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/projectforge/rest/poll/PollPageRest.kt | 17 ++++++++++++++--- .../projectforge/rest/poll/ResponsePageRest.kt | 6 +++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 77a16b2d4a..9e804b26ef 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -93,14 +93,16 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val layout = super.createEditLayout(dto, userAccess) val fieldset = UIFieldset(UILength(12)) if(dto.id != null){ - fieldset.add(UIRow() + fieldset .add(UIButton.createDefaultButton( id = "response-poll-button", responseAction = ResponseAction(PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "?pollid=${dto.id}&questionOwner=${dto.delegationUser?.id}", targetType = TargetType.REDIRECT), title = "poll.response.poll" )) - .add(UIInput(id = "delegationUser", label = "poll.delegation_label", dataType = UIDataType.USER)) - )} + if(hasFullAccess(dto)){ + fieldset.add(UIInput(id = "delegationUser", label = "poll.delegation_label", dataType = UIDataType.USER)) + } + } fieldset .add(lc, "title", "description", "location") .add(lc, "owner") @@ -398,4 +400,13 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. else return UIInput(id, label = label, dataType = dataType) } + + private fun hasFullAccess(dto: Poll): Boolean{ + val loggedInUser = ThreadLocalUserContext.user + val foundUser = dto.fullAccessUsers?.any { user -> user.id == loggedInUser?.id } + if(foundUser == true || dto.owner?.id == loggedInUser?.id) { + return true + } + return false + } } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 03713563c9..cc59aa6339 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -52,16 +52,16 @@ class ResponsePageRest : AbstractDynamicPageRest() { else questionOwner = ThreadLocalUserContext.user?.id - + val questionOwnerName = userService.getUser(questionOwner).displayName val pollDto = transformPollFromDB(pollData) val layout = UILayout("poll.response.title") - val fieldSet = UIFieldset(12, title = pollDto.title) + val fieldSet = UIFieldset(12, title = pollDto.title + " Antworten von " + questionOwnerName) fieldSet .add(UIReadOnlyField(value = pollDto.description, label = "Description")) .add(UIReadOnlyField(value = pollDto.location, label = "Location")) .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = "Owner")) - .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = "Deadline")) + .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = "Deadline")) layout.add(fieldSet) From e2e90d81cb8b500f16ef934622eb12a5bbbb3a23 Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Mon, 8 May 2023 14:52:45 +0200 Subject: [PATCH 077/160] merge dev into branch --- .../org/projectforge/business/poll/PollDO.kt | 3 +- .../business/poll/PollResponseDO.kt | 4 +- .../src/main/resources/application.properties | 4 +- .../migrate/common/V7.5.1.2__7.5.1.0-POLL.sql | 12 +- .../org/projectforge/rest/poll/CronJobs.kt | 29 ++-- .../rest/poll/PollInfoPageRest.kt | 76 +++++++++++ .../projectforge/rest/poll/PollPageRest.kt | 129 ++++++++++-------- .../rest/poll/ResponsePageRest.kt | 14 +- 8 files changed, 187 insertions(+), 84 deletions(-) create mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 1a6417b667..17f88704b2 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -1,6 +1,7 @@ package org.projectforge.business.poll import org.hibernate.search.annotations.Indexed +import org.hibernate.search.annotations.IndexedEmbedded import org.projectforge.business.common.BaseUserGroupRightsDO import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.persistence.api.AUserRightId @@ -28,7 +29,7 @@ open class PollDO : DefaultBaseDO() { @get:PropertyInfo(i18nKey = "poll.owner") @get:ManyToOne(fetch = FetchType.LAZY) - @get:JoinColumn(name = "owner_pk", nullable = false) + @get:JoinColumn(name = "owner_fk", nullable = false) open var owner: PFUserDO? = null @PropertyInfo(i18nKey = "poll.location") diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt index c79f36557c..0f5399c391 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt @@ -16,12 +16,12 @@ import javax.persistence.* open class PollResponseDO : DefaultBaseDO() { @get:PropertyInfo(i18nKey = "poll.response.poll") @get:ManyToOne(fetch = FetchType.LAZY) - @get:JoinColumn(name = "poll_pk", nullable = false) + @get:JoinColumn(name = "poll_fk", nullable = false) open var poll: PollDO? = null @get:PropertyInfo(i18nKey = "poll.response.owner") @get:ManyToOne(fetch = FetchType.LAZY) - @get:JoinColumn(name = "owner_pk", nullable = false) + @get:JoinColumn(name = "owner_fk", nullable = false) open var owner: PFUserDO? = null @PropertyInfo(i18nKey = "poll.responses") diff --git a/projectforge-business/src/main/resources/application.properties b/projectforge-business/src/main/resources/application.properties index 782c9785d0..3dfed6b30d 100644 --- a/projectforge-business/src/main/resources/application.properties +++ b/projectforge-business/src/main/resources/application.properties @@ -247,9 +247,9 @@ mail.session.pfmailsession.standardEmailSender=sender@yourserver.org #Mail protocol: Plain, StartTLS,SSL mail.session.pfmailsession.encryption=Plain #Hostname of the email server -mail.session.pfmailsession.smtp.host=mail.yourserver.org +mail.session.pfmailsession.smtp.host=localhost #Port number of the email server -mail.session.pfmailsession.smtp.port=25 +mail.session.pfmailsession.smtp.port=1025 #The email server needs authentification mail.session.pfmailsession.smtp.auth=false #Authentification by user name diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql index 3d6b1fd54d..c16b83fe96 100644 --- a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql @@ -9,7 +9,7 @@ CREATE TABLE T_POLL title CHARACTER VARYING(1000) NOT NULL, description CHARACTER VARYING(1000), location CHARACTER VARYING(1000), - owner_pk INTEGER NOT NULL, + owner_fk INTEGER NOT NULL, deadline DATE NOT NULL, date DATE, state CHARACTER VARYING(1000) NOT NULL, @@ -23,7 +23,7 @@ CREATE TABLE T_POLL ALTER TABLE T_POLL ADD CONSTRAINT t_poll_pkey PRIMARY KEY (pk); ALTER TABLE T_POLL - ADD CONSTRAINT fk_t_poll_pf_user FOREIGN KEY (owner_pk) REFERENCES t_pf_user (pk); + ADD CONSTRAINT fk_t_poll_pf_user FOREIGN KEY (owner_fk) REFERENCES t_pf_user (pk); CREATE TABLE T_POLL_RESPONSE ( @@ -31,14 +31,14 @@ CREATE TABLE T_POLL_RESPONSE deleted BOOLEAN NOT NULL, created TIMESTAMP WITHOUT TIME ZONE, last_update TIMESTAMP WITHOUT TIME ZONE, - poll_pk INTEGER NOT NULL, - owner_pk INTEGER NOT NULL, + poll_fk INTEGER NOT NULL, + owner_fk INTEGER NOT NULL, responses CHARACTER Varying(10000) ); ALTER TABLE T_POLL_RESPONSE ADD CONSTRAINT t_poll_response_pkey PRIMARY KEY (pk); ALTER TABLE T_POLL_RESPONSE - ADD CONSTRAINT fk_t_poll_response_pf_user FOREIGN KEY (owner_pk) REFERENCES t_pf_user (pk); + ADD CONSTRAINT fk_t_poll_response_pf_user FOREIGN KEY (owner_fk) REFERENCES t_pf_user (pk); ALTER TABLE T_POLL_RESPONSE - ADD CONSTRAINT fk_t_poll_response_poll FOREIGN KEY (poll_pk) REFERENCES t_poll (pk); \ No newline at end of file + ADD CONSTRAINT fk_t_poll_response_poll FOREIGN KEY (poll_fk) REFERENCES t_poll (pk); \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt index f206c513d4..3067feb52d 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt @@ -25,6 +25,18 @@ class CronJobs { @Autowired private lateinit var pollMailService: PollMailService + + /** + * Cron job for daily stuff + */ +// @Scheduled(cron = "0 0 1 * * *") // 1am everyday + @Scheduled(cron = "0 * * * * *") // 1am everyday + fun dailyCronJobs() { + cronDeletePolls() + cronEndPolls() + } + + /** * Method to end polls after deadline */ @@ -52,14 +64,12 @@ class CronJobs { override fun getFilename(): String { return it.title+ "_" + LocalDateTime.now().year +"_Result"+ ".xlsx" } - override fun getContent(): ByteArray? { return exel } } list.add(attachment) - header = "Umfrage ist abgelaufen" mail =""" Die Umfrage ist zu ende. Hier die ergebnisse. @@ -95,8 +105,7 @@ class CronJobs { } - - if(mail.isNotEmpty()){ + if (mail.isNotEmpty()) { pollMailService.sendMail(to="test", subject = header, content = mail, mailAttachments = list) } } @@ -106,21 +115,11 @@ class CronJobs { } - /** - * Cron job for daily stuff - */ - @Scheduled(cron = "0 0 1 * * *") // 1am everyday - fun dailyCronJobs() { - cronDeletePolls() - cronEndPolls() - } - - /** * Method to delete old polls */ fun cronDeletePolls() { - // check if poll end in Future + // check if poll end in future val polls = pollDao.internalLoadAll() val pollsMoreThanOneYearPast = polls.filter { it.created?.before(Date.from(LocalDate.now().minusYears(1).atStartOfDay( ZoneId.systemDefault() diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt new file mode 100644 index 0000000000..3fbff669bb --- /dev/null +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt @@ -0,0 +1,76 @@ +package org.projectforge.rest.poll + +import org.projectforge.rest.config.Rest +import org.projectforge.rest.core.AbstractDynamicPageRest +import org.projectforge.rest.core.PagesResolver +import org.projectforge.rest.dto.FormLayoutData +import org.projectforge.ui.* +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import javax.servlet.http.HttpServletRequest + + +@RestController +@RequestMapping("${Rest.URL}/pollInfo") +class PollInfoPageRest: AbstractDynamicPageRest() { + + @GetMapping("dynamic") + fun getForm(request: HttpServletRequest): FormLayoutData { + + val layout = UILayout("poll.infopage") + val field = UIFieldset() + .add(UILabel(""" Anleitung um eine Umfrage zu erstellen + | Als erstes werden die Parameter einer Umfrage angelegt. + """.trimMargin())) + + field.add(UICol() + .add(UIReadOnlyField("title", label = "title", value = "Test"))) + field.add(UICol() + .add(UIReadOnlyField("description", label = "description", value = "description"))) + field.add(UICol() + .add(UIReadOnlyField("location", label = "location", value = "location"))) + field.add(UICol() + .add(UIReadOnlyField("owner", label = "owner", value = "owner"))) + field.add(UICol() + .add(UIReadOnlyField("deadline", label = "deadline", value = "deadline"))) + + field.add(UIRow().add(UICol().add(UILabel("""Anschließend werden die Fragen der Umfrage angelegt. + Die Fragen können aus verschiedenen Typen bestehen. """)))) + + layout.add(field) + + layout.add(UIFieldset().add(UILabel("YesNoQuestion")).add( + UICol() + .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage die mit Ja oder Nein Beantwortet werden kann")) + )) + layout.add(UIFieldset().add(UILabel("MultipleChoiceQuestion")).add( + UICol() + .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage die mit mehreren Antworten Beantwortet werden kann")) + )) + layout.add(UIFieldset().add(UILabel("TextQuestion")).add( + UICol() + .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage die mit einer Freitext Antwort Beantwortet werden kann")) + + )) + layout.add(UIFieldset().add(UILabel("DateQuestion")).add( + UICol() + .add(UIReadOnlyField("question", label = "Question", + value = """Eine Frage ob an einem Tag (Uhrzeit) die Teilnehmer zeit haben. Die einem Ja, Nein oder Vielleicht + Beantwortet werden kann""" + )))) + layout.add(UIFieldset().add(UILabel("Dropdown")).add( + UICol() + .add(UIReadOnlyField("question", label = "Question", + value = """ Eine Frage die mit einem Dropdown Beantwortet werden kann""" + )))) + + + LayoutUtils.process(layout) + + return FormLayoutData(null, layout, createServerData(request)) + + + } + +} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 9e804b26ef..53d472775b 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -4,11 +4,9 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.projectforge.business.group.service.GroupService import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao -import org.projectforge.business.poll.PollResponseDao import org.projectforge.business.user.service.UserService import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext -import org.projectforge.framework.persistence.user.entities.PFUserDO import org.projectforge.rest.config.Rest import org.projectforge.rest.config.RestUtils import org.projectforge.rest.core.* @@ -51,7 +49,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun transformForDB(dto: Poll): PollDO { val pollDO = PollDO() dto.copyTo(pollDO) - if(dto.inputFields!= null){ + if (dto.inputFields != null) { pollDO.inputFields = ObjectMapper().writeValueAsString(dto.inputFields) } return pollDO @@ -87,22 +85,45 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } - override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { - val lc = LayoutContext(PollDO::class.java) val layout = super.createEditLayout(dto, userAccess) val fieldset = UIFieldset(UILength(12)) - if(dto.id != null){ - fieldset - .add(UIButton.createDefaultButton( + if (dto.state == PollDO.State.RUNNING && dto.id != null) { + fieldset.add( + UIButton.createDefaultButton( id = "response-poll-button", - responseAction = ResponseAction(PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "?pollid=${dto.id}&questionOwner=${dto.delegationUser?.id}", targetType = TargetType.REDIRECT), + responseAction = ResponseAction( + PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "${dto.id}", + targetType = TargetType.REDIRECT + ), title = "poll.response.poll" - )) + ) + ) if(hasFullAccess(dto)){ fieldset.add(UIInput(id = "delegationUser", label = "poll.delegation_label", dataType = UIDataType.USER)) } } + fieldset.add( + UIRow().add( + UICol( + UILength(10) + ) + ).add( + UICol( + UILength(1) + ).add( + UIButton.createLinkButton( + id = "poll-guide",title= "Poll Guide", responseAction = ResponseAction( + PagesResolver.getDynamicPageUrl( + PollInfoPageRest::class.java, absolute = true + ), targetType = TargetType.MODAL + ) + ) + ) + ) + ) + + fieldset .add(lc, "title", "description", "location") .add(lc, "owner") @@ -111,53 +132,53 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. .add(UISelect.createGroupSelect(lc, "fullAccessGroups", true, "poll.fullAccessGroups")) .add(UISelect.createUserSelect(lc, "attendees", true, "poll.attendees")) .add(UISelect.createGroupSelect(lc, "groupAttendees", true, "poll.groupAttendees")) - if(dto.id == null) { - fieldset.add( + if (dto.id == null) { + fieldset.add( + UIRow() + .add( + UICol(UILength(xs = 9, sm = 9, md = 9, lg = 9)) + .add( + UISelect( + "questionType", + values = BaseType.values().map { UISelectValue(it, it.name) }, + label = "questionType" + ) + ) + ) + .add( + UICol(UILength(xs = 3, sm = 3, md = 3, lg = 3)) + .add( + UIButton.createDefaultButton( + id = "add-question-button", + responseAction = ResponseAction( + "${Rest.URL}/poll/add", + targetType = TargetType.POST + ), + title = "Eigene Frage hinzufügen" + ) + ) + ) + ) + .add( UIRow() .add( UICol(UILength(xs = 9, sm = 9, md = 9, lg = 9)) - .add( - UISelect( - "questionType", - values = BaseType.values().map { UISelectValue(it, it.name) }, - label = "questionType" - ) - ) ) .add( UICol(UILength(xs = 3, sm = 3, md = 3, lg = 3)) .add( UIButton.createDefaultButton( - id = "add-question-button", + id = "micromata-vorlage-button", responseAction = ResponseAction( - "${Rest.URL}/poll/add", + "${Rest.URL}/poll/addPremadeQuestions", targetType = TargetType.POST ), - title = "Eigene Frage hinzufügen" + title = "Micromata Vorlage nutzen" ) ) ) ) - .add( - UIRow() - .add( - UICol(UILength(xs = 9, sm = 9, md = 9, lg = 9)) - ) - .add( - UICol(UILength(xs = 3, sm = 3, md = 3, lg = 3)) - .add( - UIButton.createDefaultButton( - id = "micromata-vorlage-button", - responseAction = ResponseAction( - "${Rest.URL}/poll/addPremadeQuestions", - targetType = TargetType.POST - ), - title = "Micromata Vorlage nutzen" - ) - ) - ) - ) - } + } layout.add(fieldset) addQuestionFieldset(layout, dto) @@ -195,8 +216,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) } - - // PostMapping add @PostMapping("/add") fun addQuestionField( @@ -277,26 +296,26 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.inputFields?.forEachIndexed { index, field -> val objGiven = dto.id != null //differentiates between initial creation and editing val fieldset = UIFieldset(UILength(12), title = field.type.toString()) - if(!objGiven){ - fieldset.add(generateDeleteButton(layout, field.uid)) - } + if(!objGiven){ + fieldset.add(generateDeleteButton(layout, field.uid)) + } fieldset.add(getUiElement(objGiven, "inputFields[${index}].question", "Frage")) if (field.type == BaseType.SingleResponseQuestion || field.type == BaseType.MultiResponseQuestion) { field.answers?.forEachIndexed { answerIndex, _ -> fieldset.add(generateSingleAndMultiResponseAnswer(objGiven, index, field.uid, answerIndex, layout)) } - if(!objGiven) { - fieldset.add( - UIRow().add( - UIButton.createAddButton( - responseAction = ResponseAction( - "${Rest.URL}/poll/addAnswer/${field.uid}", targetType = TargetType.POST + if(!objGiven) { + fieldset.add( + UIRow().add( + UIButton.createAddButton( + responseAction = ResponseAction( + "${Rest.URL}/poll/addAnswer/${field.uid}", targetType = TargetType.POST + ) ) ) ) - ) - } + } } layout.add(fieldset) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index cc59aa6339..a45924ab15 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -6,8 +6,10 @@ import org.projectforge.business.poll.PollDao import org.projectforge.business.poll.PollResponseDO import org.projectforge.business.poll.PollResponseDao import org.projectforge.business.user.service.UserService +import org.projectforge.framework.access.AccessChecker +import org.projectforge.framework.access.AccessCheckerImpl.I18N_KEY_VIOLATION_USER_NOT_MEMBER_OF +import org.projectforge.framework.access.AccessException import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext -import org.projectforge.framework.persistence.user.entities.PFUserDO import org.projectforge.framework.utils.NumberHelper import org.projectforge.rest.config.Rest import org.projectforge.rest.core.AbstractDynamicPageRest @@ -20,9 +22,8 @@ import org.projectforge.ui.* import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* -import java.util.UUID +import java.util.* import javax.servlet.http.HttpServletRequest -import org.projectforge.rest.dto.User @RestController @@ -38,6 +39,9 @@ class ResponsePageRest : AbstractDynamicPageRest() { @Autowired private lateinit var userService: UserService + @Autowired + private lateinit var accesscheck: AccessChecker + @GetMapping("dynamic") fun getForm(request: HttpServletRequest, @RequestParam("pollid") pollStringId: String?, @RequestParam("questionOwner") delUser: String?): FormLayoutData { val id = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") @@ -55,6 +59,10 @@ class ResponsePageRest : AbstractDynamicPageRest() { val questionOwnerName = userService.getUser(questionOwner).displayName val pollDto = transformPollFromDB(pollData) + if (pollDto.state == PollDO.State.FINISHED) { + throw AccessException(I18N_KEY_VIOLATION_USER_NOT_MEMBER_OF, "Umfrage wurde bereits beendet"); + } + val layout = UILayout("poll.response.title") val fieldSet = UIFieldset(12, title = pollDto.title + " Antworten von " + questionOwnerName) fieldSet From e740c1e99c7a373c7b2c217a8add03aebe3a2d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Wed, 10 May 2023 09:41:15 +0200 Subject: [PATCH 078/160] Fixed deadline check --- .../src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt index 3067feb52d..f28518dda8 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt @@ -48,7 +48,7 @@ class CronJobs { val list = ArrayList() // set State.FINISHED for all old polls polls.forEach { - if (it.deadline?.isBefore(LocalDate.now().minusDays(1)) == true) { + if (it.deadline?.isBefore(LocalDate.now()) == true) { it.state = PollDO.State.FINISHED // check if state is open or closed @@ -91,7 +91,7 @@ class CronJobs { mail =""" Sehr geehrter Teilnehmer,wir laden Sie herzlich dazu ein, an unserer Umfrage zum Thema ${it.title} teilzunehmen. Ihre Meinung ist uns sehr wichtig und wir würden uns freuen, wenn Sie uns dabei helfen könnten, - unsere Forschungsergebnisse zu verbessern. Für diese Umfrage ist ${it ///owmer + unsere Forschungsergebnisse zu verbessern. Für diese Umfrage ist ${it ///owner }zuständig. Bei Fragen oder Anmerkungen können Sie sich gerne an ihn wenden. Bitte beachten Sie, dass das Enddatum für die Teilnahme an dieser Umfrage der ${it.deadline.toString()} ist. From 2bad57f8a6f45a9c0ec3816262fef7724668ada5 Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Wed, 10 May 2023 14:12:13 +0200 Subject: [PATCH 079/160] Show modified state in overview list --- .../src/main/kotlin/org/projectforge/rest/poll/Poll.kt | 7 ++++++- .../kotlin/org/projectforge/rest/poll/PollPageRest.kt | 9 +++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index a66788f64f..8c649384d9 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -21,7 +21,8 @@ class Poll( var fullAccessGroups: List? = null, var fullAccessUsers: List? = null, var groupAttendees: List? = null, - var attendees: List? = null + var attendees: List? = null, + private var frontendState: String? = null ) : BaseDTO() { override fun copyFrom(src: PollDO) { super.copyFrom(src) @@ -29,6 +30,10 @@ class Poll( fullAccessUsers = User.toUserList(src.fullAccessUserIds) groupAttendees = Group.toGroupList(src.groupAttendeeIds) attendees = User.toUserList(src.attendeeIds) + frontendState = if (state == PollDO.State.RUNNING) + "Endet am $deadline" + else + "Beendet" } override fun copyTo(dest: PollDO) { diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index acb1bcfbe0..75debf2c59 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -87,8 +87,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. this, userAccess = userAccess, ) - .add(lc, "title", "description", "location", "owner", "deadline", "date", "state") - + .add(lc, "title", "description", "location", "owner", "deadline", "date") + .add(UIAgGridColumnDef.createCol( + field = "frontendState", + headerName = "State" + )) } override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { @@ -274,8 +277,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return filters } - - override fun onWatchFieldsUpdate( request: HttpServletRequest, dto: Poll, From 85f7273e7cd50e2c0d2939918399e594c2204d9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Wed, 10 May 2023 14:34:51 +0200 Subject: [PATCH 080/160] added read only to all fields and restricted user access --- .../projectforge/rest/poll/PollPageRest.kt | 79 +++++++++++++++---- 1 file changed, 63 insertions(+), 16 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index e03f3c81be..e62bc473d5 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -8,7 +8,6 @@ import org.projectforge.business.user.service.UserService import org.projectforge.framework.access.AccessException import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext -import org.projectforge.menu.MenuItem import org.projectforge.rest.config.Rest import org.projectforge.rest.config.RestUtils import org.projectforge.rest.core.* @@ -42,6 +41,9 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @Autowired private lateinit var pollMailService: PollMailService + @Autowired + private lateinit var pollDao: PollDao + override fun newBaseDTO(request: HttpServletRequest?): Poll { val result = Poll() result.owner = ThreadLocalUserContext.user @@ -112,7 +114,9 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. UILength(1) ).add( UIButton.createLinkButton( - id = "poll-guide",title= "Poll Guide", responseAction = ResponseAction( + id = "poll-guide", + title = "Poll Guide", + responseAction = ResponseAction( PagesResolver.getDynamicPageUrl( PollInfoPageRest::class.java, absolute = true ), targetType = TargetType.MODAL @@ -123,10 +127,9 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) + addDefaultParameterFields(dto, fieldset, isRunning = dto.state == PollDO.State.RUNNING) + fieldset - .add(lc, "title", "description", "location") - .add(lc, "owner") - .add(lc, "deadline", "date") .add(UISelect.createUserSelect(lc, "fullAccessUsers", true, "poll.fullAccessUsers")) .add(UISelect.createGroupSelect(lc, "fullAccessGroups", true, "poll.fullAccessGroups")) .add(UISelect.createUserSelect(lc, "attendees", true, "poll.attendees")) @@ -149,11 +152,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. .add( UIButton.createDefaultButton( id = "add-question-button", + title = "Eigene Frage hinzufügen", responseAction = ResponseAction( "${Rest.URL}/poll/add", targetType = TargetType.POST - ), - title = "Eigene Frage hinzufügen" + ) ) ) ) @@ -213,7 +216,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @PathVariable("fieldId") fieldUid: String, ): ResponseEntity { val dto = postData.data - val userAccess = UILayout.UserAccess(insert = true, update = true) + val userAccess = getUserAccess(dto) val found = dto.inputFields?.find { it.uid == fieldUid } found?.answers?.add("") @@ -230,8 +233,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. fun addQuestionField( @RequestBody postData: PostData ): ResponseEntity { - val userAccess = UILayout.UserAccess(insert = true, update = true) val dto = postData.data + val userAccess = getUserAccess(dto) var type = BaseType.valueOf(dto.questionType ?: "TextQuestion") var question = Question(uid = UUID.randomUUID().toString(), type = type) @@ -284,8 +287,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. private fun addPremadeQuestionsField( @RequestBody postData: PostData, ): ResponseEntity { - val userAccess = UILayout.UserAccess(insert = true, update = true) val dto = postData.data + val userAccess = getUserAccess(dto) PREMADE_QUESTIONS.entries.forEach { entry -> dto.inputFields?.add(entry.value) @@ -295,9 +298,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) } + + private fun addQuestionFieldset(layout: UILayout, dto: Poll) { dto.inputFields?.forEachIndexed { index, field -> - val objGiven = dto.id != null //differentiates between initial creation and editing + val objGiven = dto.id != null // differentiates between initial creation and editing val fieldset = UIFieldset(UILength(12), title = field.type.toString()) if(!objGiven){ fieldset.add(generateDeleteButton(layout, field.uid)) @@ -308,7 +313,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. field.answers?.forEachIndexed { answerIndex, _ -> fieldset.add(generateSingleAndMultiResponseAnswer(objGiven, index, field.uid, answerIndex, layout)) } - if(!objGiven) { + if (!objGiven) { fieldset.add( UIRow().add( UIButton.createAddButton( @@ -356,7 +361,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @PathVariable("answerIndex") answerIndex: Int ): ResponseEntity { val dto = postData.data - val userAccess = UILayout.UserAccess(insert = true, update = true) + val userAccess = getUserAccess(dto) dto.inputFields?.find { it.uid.equals(questionUid) }?.answers?.removeAt(answerIndex) dto.owner = userService.getUser(dto.owner?.id) @@ -408,18 +413,60 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. .getExcel(poll) val filename = ("test.xlsx") - if (bytes == null || bytes.size == 0) { + if (bytes == null || bytes.isEmpty()) { log.error("Oops, xlsx has zero size. Filename: $filename") - return null; + return null } return RestUtils.downloadFile(filename, bytes) } - //once created, questions should be ReadOnly + /** + * Once created, questions should be ReadOnly + */ private fun getUiElement(obj: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement{ if (obj) return UIReadOnlyField(id, label = label, dataType = dataType) else return UIInput(id, label = label, dataType = dataType) } + + private fun addDefaultParameterFields(pollDto: Poll, fieldset: UIFieldset, isRunning: Boolean) { + if (isRunning) { + fieldset + .add(lc, "title", "description", "location") + .add(UISelect.createUserSelect(lc, "owner", false, "poll.owner")) + .add(lc, "deadline", "date") + } + else { + fieldset + .add(UIReadOnlyField(value = pollDto.title, label = "titel", dataType = UIDataType.STRING)) + .add(UIReadOnlyField(value = pollDto.description, label = "description", dataType = UIDataType.STRING)) + .add(UIReadOnlyField(value = pollDto.location, label = "location", dataType = UIDataType.STRING)) + .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = "deadline", dataType = UIDataType.STRING)) + .add(UIReadOnlyField(value = (pollDto.date?.toString() ?: ""), label = "date", dataType = UIDataType.STRING)) + .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = "owner", dataType = UIDataType.STRING)) + } + } + + + /** + * restricts the user access accordingly + */ + private fun getUserAccess(pollDto: Poll): UILayout.UserAccess { + val pollDO = PollDO() + pollDto.copyTo(pollDO) + + return if (pollDao.hasFullAccess(pollDO) == false) { + // no full access user + UILayout.UserAccess(insert = false, update = false, delete = false, history = false) + } else { + if (pollDto.id == null) { + // full access when creating new poll + UILayout.UserAccess(insert = true, update = true, delete = false, history = true) + } else { + // full access when viewing old poll + UILayout.UserAccess(insert = false, update = false, delete = true, history = false) + } + } + } } From 469c37ec98dd421888f0818ad1989a1a0860d188 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 10 May 2023 14:45:11 +0200 Subject: [PATCH 081/160] excel wird richtig ertellt, aber die rowHeight is not perfekt --- .../rest/poll/Exel/ExcelExport.kt | 67 +++++++++++++------ .../projectforge/rest/poll/PollPageRest.kt | 6 +- .../projectforge/rest/poll/PollResponse.kt | 13 ++-- .../rest/poll/ResponsePageRest.kt | 14 ++-- 4 files changed, 67 insertions(+), 33 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt index 347cd1a3d2..651df57b51 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt @@ -4,7 +4,6 @@ import de.micromata.merlin.excel.ExcelRow import de.micromata.merlin.excel.ExcelSheet import de.micromata.merlin.excel.ExcelWorkbook import org.apache.poi.ss.usermodel.CellStyle -import org.apache.poi.ss.usermodel.Font import org.apache.poi.ss.usermodel.HorizontalAlignment import org.apache.poi.ss.util.CellRangeAddress import org.projectforge.business.poll.PollDao @@ -30,6 +29,7 @@ class ExcelExport { @Autowired private lateinit var pollResponseDao: PollResponseDao + @Autowired private lateinit var pollDao: PollDao @@ -57,7 +57,6 @@ class ExcelExport { responses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } setNewRows(excelSheet,poll, user, res, index) } - return returnByteFile(excelSheet) } } catch (e: NullPointerException) { @@ -71,33 +70,33 @@ class ExcelExport { private fun setFirstRow(excelSheet: ExcelSheet,style: CellStyle,poll: Poll){ val excelRow = excelSheet.getRow(0) val excelRow1 = excelSheet.getRow(1) - var counter = 0 style.alignment = HorizontalAlignment.CENTER - var merge = 0 + var merge = 1 poll.inputFields?.forEach{question -> - merge += 1 if(question.type==BaseType.MultiResponseQuestion || - question.type==BaseType.SingleResponseQuestion || - question.type==BaseType.DateQuestion) { + question.type==BaseType.SingleResponseQuestion ) { + var counter = merge question.answers?.forEach { answer -> - counter++ excelRow1.getCell(counter).setCellValue(answer) excelRow1.getCell(counter).setCellStyle(style) excelSheet.autosize(counter) + counter++ } excelRow.getCell(merge).setCellValue(question.question) - merge += question.answers?.size ?: 0 - //excelSheet.addMergeRegion(CellRangeAddress(0,0,merge,merge + counter)) excelSheet.autosize(merge) - + // cuter -1 because the + counter-- + excelSheet.addMergeRegion(CellRangeAddress(0,0,merge,counter)) + merge = counter } else { excelRow.getCell(merge).setCellValue(question.question) excelRow.setCellStyle(style) excelSheet.autosize(merge) } + merge += 1 } excelRow.setHeight(30F) } @@ -105,30 +104,54 @@ class ExcelExport { val excelRow = excelSheet.getRow(FIRST_DATA_ROW_NUM + index) - excelRow.getCell(0).setCellValue(user.displayName) excelSheet.autosize(0) - var chell=0 + var CELL=0 + + var largestAwsnser=""; poll.inputFields?.forEachIndexed{i, question -> - val answer = res?.responses?.find { it.questionUid == question.uid } + val questionAnswer = res?.responses?.find { it.questionUid == question.uid } - answer?.answers?.forEachIndexed { ind, antwort -> - chell++ + if (questionAnswer?.answers.isNullOrEmpty()){ + CELL += question.answers?.size?:0 + } + questionAnswer?.answers?.forEachIndexed { ind, antwort -> + CELL++ if (question.type == BaseType.SingleResponseQuestion || question.type == BaseType.MultiResponseQuestion){ if(antwort is Boolean && antwort == true){ - excelRow.getCell(chell).setCellValue("X") + excelRow.getCell(CELL).setCellValue("X") + } + if(antwort is String && antwort.equals(question.answers?.get(ind))){ + excelRow.getCell(CELL).setCellValue("X") } } - else { - excelRow.getCell(chell).setCellValue(antwort.toString()) + else{ + excelRow.getCell(CELL).setCellValue(antwort.toString()) + if(countLines(antwort.toString()) > countLines(largestAwsnser)){ + largestAwsnser = antwort.toString() + } } - excelSheet.autosize(chell) + excelSheet.autosize(CELL) } } - excelRow.setHeight(20F) - } + val puffer: String = largestAwsnser + var counterOfBreaking = 0 + var counterOfOverlength = 0 + val pufferSplit: Array = puffer.split("\r\n|\r|\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + // check for line-breaks + for (i in pufferSplit.indices) { + counterOfBreaking++ + counterOfOverlength += pufferSplit.get(i).length / 70 + } + excelRow.setHeight((14 + counterOfOverlength * 14 + counterOfBreaking * 14).toFloat()) + //excelRow.setHeight(20F) ///TODO LEON FIX THIS PROBLEM + } + private fun countLines(str: String): Int { + val lines = str.split("\r\n|\r|\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + return lines.size + } private fun createNewRow(excelSheet: ExcelSheet?, emptyRow: ExcelRow?, anzNewRows: Int) { if (excelSheet == null || emptyRow == null) { diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 9945b54989..2c013965f0 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -107,7 +107,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ), title = "poll.response.poll" ) - ) + ).add(UIButton.createExportButton( + id = "export-poll-response-button", + responseAction = ResponseAction("${Rest.URL}/poll/export/${dto.id}", targetType = TargetType.POST), + title = "poll.export.response.poll" + )) } fieldset.add( UIRow().add( diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt index d29cdfb1db..525d0d97a9 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt @@ -9,8 +9,7 @@ import org.projectforge.rest.dto.BaseDTO class PollResponse: BaseDTO() { var poll: PollDO? = null var owner: PFUserDO? = null - var responses: MutableList? = mutableListOf() - + var responses: MutableList? = mutableListOf() override fun copyTo(dest: PollResponseDO) { if (!this.responses.isNullOrEmpty()) { dest.responses = ObjectMapper().writeValueAsString(this.responses) @@ -21,18 +20,20 @@ class PollResponse: BaseDTO() { override fun copyFrom(src: PollResponseDO) { if (!src.responses.isNullOrEmpty()) { val a = ObjectMapper().readValue(src.responses, MutableList::class.java) - this.responses = a.map { Answer().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() + this.responses = a.map { QuestionAnswer().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() } super.copyFrom(src) } } -class Answer { +class QuestionAnswer { var uid: String? = null var questionUid: String? = "" var answers: MutableList? = mutableListOf() - fun toObject(string:String): Answer { - return ObjectMapper().readValue(string, Answer::class.java) + + + fun toObject(string:String): QuestionAnswer { + return ObjectMapper().readValue(string, QuestionAnswer::class.java) } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 4d05f46e3c..e16b4e4305 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -69,14 +69,14 @@ class ResponsePageRest : AbstractDynamicPageRest() { pollDto.inputFields?.forEachIndexed { index, field -> val fieldSet2 = UIFieldset(title = field.question) - val answer = Answer() - answer.uid = UUID.randomUUID().toString() - answer.questionUid = field.uid + val questionAnswer = QuestionAnswer() + questionAnswer.uid = UUID.randomUUID().toString() + questionAnswer.questionUid = field.uid pollResponse.responses?.firstOrNull { it.questionUid == field.uid }.let { if (it == null) - pollResponse.responses?.add(answer) + pollResponse.responses?.add(questionAnswer) } val col = UICol() @@ -102,6 +102,9 @@ class ResponsePageRest : AbstractDynamicPageRest() { } if (field.type == BaseType.MultiResponseQuestion) { field.answers?.forEachIndexed { index2, _ -> + if(pollResponse.responses?.get(index)?.answers?.getOrNull(index2) == null){ + pollResponse.responses?.get(index)?.answers?.add(index2, false) + } col.add(UICheckbox("responses[$index].answers[$index2]", label = field.answers?.get(index2) ?: "")) } } @@ -133,6 +136,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { val pollResponseDO = PollResponseDO() postData.data.copyTo(pollResponseDO) + pollResponseDO.owner = ThreadLocalUserContext.user pollResponseDao.internalLoadAll().firstOrNull { pollResponse -> @@ -149,7 +153,9 @@ class ResponsePageRest : AbstractDynamicPageRest() { ) } + pollResponseDao.saveOrUpdate(pollResponseDO) + return ResponseEntity.ok( ResponseAction( targetType = TargetType.REDIRECT, From c012e0b40e17c51901c4ec9987ce1d898f7d6d3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Wed, 10 May 2023 16:01:44 +0200 Subject: [PATCH 082/160] Restricted access to not existing poll id --- .../projectforge/rest/poll/ResponsePageRest.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 1aabe2895b..8b1b31f8e6 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -1,10 +1,9 @@ package org.projectforge.rest.poll import com.fasterxml.jackson.databind.ObjectMapper -import org.projectforge.business.poll.PollDO -import org.projectforge.business.poll.PollDao -import org.projectforge.business.poll.PollResponseDO -import org.projectforge.business.poll.PollResponseDao +import org.projectforge.business.poll.* +import org.projectforge.framework.access.AccessCheckerImpl.I18N_KEY_VIOLATION_USER_NOT_MEMBER_OF +import org.projectforge.framework.access.AccessException import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.utils.NumberHelper import org.projectforge.rest.config.Rest @@ -37,6 +36,15 @@ class ResponsePageRest : AbstractDynamicPageRest() { val pollData = pollDao.internalGetById(id) ?: PollDO() val pollDto = transformPollFromDB(pollData) + // todo I18N_keys ändern + if (pollData.id == null) { + throw AccessException(I18N_KEY_VIOLATION_USER_NOT_MEMBER_OF, "Umfrage nicht gefunden.") + } + + if (pollData.getPollAssignment().contains(PollAssignment.ATTENDEE)) { + throw AccessException(I18N_KEY_VIOLATION_USER_NOT_MEMBER_OF, "Du darfst nicht auf diese Umfrage antworten.") + } + val layout = UILayout("poll.response.title") val fieldSet = UIFieldset(12, title = pollDto.title) fieldSet From b48c4caf964e64c61cbf5bebccafb22465354028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Thu, 11 May 2023 09:31:10 +0200 Subject: [PATCH 083/160] Added AccessException --- .../main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 8b1b31f8e6..dd9815a1ef 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -32,7 +32,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { @GetMapping("dynamic") fun getForm(request: HttpServletRequest, @RequestParam("id") pollStringId: String?): FormLayoutData { - val id = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") + val id = NumberHelper.parseInteger(pollStringId) ?: throw AccessException(I18N_KEY_VIOLATION_USER_NOT_MEMBER_OF, "Umfrage nicht gefunden.") val pollData = pollDao.internalGetById(id) ?: PollDO() val pollDto = transformPollFromDB(pollData) From 05c72248616e5cb98da6adfbdccdd5e2516ea59a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 16 May 2023 08:23:38 +0200 Subject: [PATCH 084/160] Added i18n for all labels --- .../main/resources/I18nResources.properties | 33 +++++++--- .../resources/I18nResources_de.properties | 27 ++++++++ .../kotlin/org/projectforge/rest/poll/Poll.kt | 3 + .../projectforge/rest/poll/PollPageRest.kt | 65 ++++++++++--------- 4 files changed, 88 insertions(+), 40 deletions(-) diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index 18c0e8bbfc..be859d9990 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -1686,6 +1686,7 @@ menu.personalStatistics=My statistics menu.phoneCall=Direct call menu.pluginAdmin=Plugins menu.plugins.teamcal=List of calendars +menu.poll=Polls menu.projectmanagement=Project management menu.reindexAllDatabaseEntries=Re-build whole search index menu.reindexAllDatabaseEntries.tooltip.content=The whole index will be rebuilt. This may take a while (serveral minute for e. g. 100.000 entries) and the system performance is affected. Therefore this feature is only enabled for administrators. @@ -1973,17 +1974,31 @@ plugins.teamcal.title.add=Add calendar plugins.teamcal.title.edit=Edit team calendar plugins.teamcal.title.heading=Calendar plugins.teamcal.title.list=List of calendars -poll=Poll -poll.title=Title -poll.description=Description -poll.owner=Owner -poll.location=Location -poll.deadline=Deadline -poll.date=Date +# poll plugin +poll.answer=Answer poll.attendees=Attendees -poll.group_attendees=Attendee Groups -poll.response.poll=Poll Answer Page +poll.button.addQuestion=Add own question +poll.button.micromataTemplate=Use Micromata Template +poll.confirmation.creation=Do you really want to create the poll? You won't be able to edit the questions afterwards. +poll.confirmation.deleteAnswer=Do you really want to delete this answer? +poll.confirmation.deleteQuestion=Do you really want to delete this question? +poll.date=Date +poll.deadline=Deadline +poll.description=Description poll.error.oneQuestionRequired=At least one question is required. +poll.fullAccessGroups=Full Access Groups +poll.fullAccessUsers=Full Access User +poll.groupAttendees=Attendee Groups +poll.guide=Poll Guide +poll.location=Location +poll.owner=Owner +poll.question.textQuestion=Text Question +poll.questionType=Question Type +poll.response.page=Poll Response Page +poll.state=State +poll.title=Title +poll.title.add=Create new Poll +poll=Poll projectmanagement.personDays=Person days projectmanagement.personDays.short=pd question.deleteQuestion=Do your really want to delete this object finally? diff --git a/projectforge-business/src/main/resources/I18nResources_de.properties b/projectforge-business/src/main/resources/I18nResources_de.properties index 5692bc6eeb..8704a45753 100644 --- a/projectforge-business/src/main/resources/I18nResources_de.properties +++ b/projectforge-business/src/main/resources/I18nResources_de.properties @@ -1778,6 +1778,7 @@ menu.personalStatistics=Meine Statistiken menu.phoneCall=Direktwahl ### not translated: menu.pluginAdmin=Plugins menu.plugins.teamcal=Kalenderliste +menu.poll=Umfragen menu.projectmanagement=Projektmanagement menu.reindexAllDatabaseEntries=Suchindex voll indizieren menu.reindexAllDatabaseEntries.tooltip.content=Der Suchindex wird komplett neu aufgebaut. Bei vielen Einträgen (z. B. 100.000) kann das einige Minuten dauern und die Systemperformance beinträchtigt werden. Deshalb steht diese Funktion nur Administratoren zur Verfügung. @@ -2065,6 +2066,32 @@ plugins.teamcal.title.add=Kalender hinzufügen plugins.teamcal.title.edit=Team-Kalender bearbeiten plugins.teamcal.title.heading=Kalender plugins.teamcal.title.list=Kalenderliste +# poll plugin +poll.answer=Antwort +poll.attendees=Teilnehmer +poll.button.addQuestion=Eigene Frage hinzufügen +poll.button.micromataTemplate=Micromata Template verwenden +poll.confirmation.creation=Möchtest du die Umfrage wirklich erstellen? Du kannst die Fragen danach nicht mehr bearbeiten. +poll.confirmation.deleteAnswer=Möchtest du diese Antwort wirklich löschen? +poll.confirmation.deleteQuestion=Möchtest du diese Frage wirklich löschen? +poll.date=Datum +poll.deadline=Antwortfrist +poll.description=Beschreibung +poll.error.oneQuestionRequired=Mindestens eine Frage ist erforderlich. +poll.fullAccessGroups=Gruppen mit Vollzugriff +poll.fullAccessUsers=Benutzer mit Vollzugriff +poll.groupAttendees=Teilnehmergruppen +poll.guide=Anleitung +poll.location=Ort +poll.owner=Eigentümer +poll.question.textQuestion=Textfrage +poll.questionType=Fragen Typ +poll.response.page=Seite zur Umfrageantwort +poll.state=Status +poll.title=Titel +poll.title.add=Neue Umfrage erstellen +poll=Umfrage +poll.mail. projectmanagement.personDays=Personentage projectmanagement.personDays.short=PT question.deleteQuestion=Soll das Objekt wirklich unwiderruflich gelöscht werden? diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index 15a8f96355..7433c816c3 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -47,4 +47,7 @@ class Poll( } } + fun isAlreadyCreated(): Boolean { + return id != null + } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 6acd428c11..1123f13341 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -9,7 +9,6 @@ import org.projectforge.business.user.service.UserService import org.projectforge.framework.access.AccessException import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext -import org.projectforge.menu.MenuItem import org.projectforge.rest.config.Rest import org.projectforge.rest.config.RestUtils import org.projectforge.rest.core.* @@ -97,7 +96,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val layout = super.createEditLayout(dto, userAccess) val fieldset = UIFieldset(UILength(12)) - if (dto.state == PollDO.State.RUNNING && dto.id != null) { + if (dto.state == PollDO.State.RUNNING && dto.isAlreadyCreated()) { fieldset.add( UIButton.createDefaultButton( id = "response-poll-button", @@ -105,7 +104,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "${dto.id}", targetType = TargetType.REDIRECT ), - title = "poll.response.poll" + title = "poll.response.page" ) ).add(UIButton.createExportButton( id = "export-poll-response-button", @@ -123,7 +122,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. UILength(1) ).add( UIButton.createLinkButton( - id = "poll-guide",title= "Poll Guide", responseAction = ResponseAction( + id = "poll-guide", title = "poll.guide", responseAction = ResponseAction( PagesResolver.getDynamicPageUrl( PollInfoPageRest::class.java, absolute = true ), targetType = TargetType.MODAL @@ -142,7 +141,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. .add(UISelect.createGroupSelect(lc, "fullAccessGroups", true, "poll.fullAccessGroups")) .add(UISelect.createUserSelect(lc, "attendees", true, "poll.attendees")) .add(UISelect.createGroupSelect(lc, "groupAttendees", true, "poll.groupAttendees")) - if (dto.id == null) { + if (dto.isAlreadyCreated() == false) { fieldset.add( UIRow() .add( @@ -151,7 +150,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. UISelect( "questionType", values = BaseType.values().map { UISelectValue(it, it.name) }, - label = "questionType" + label = "poll.questionType" ) ) ) @@ -164,7 +163,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. "${Rest.URL}/poll/add", targetType = TargetType.POST ), - title = "Eigene Frage hinzufügen" + title = "poll.button.addQuestion" ) ) ) @@ -178,12 +177,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. UICol(UILength(xs = 3, sm = 3, md = 3, lg = 3)) .add( UIButton.createDefaultButton( - id = "micromata-vorlage-button", + id = "micromata-template-button", responseAction = ResponseAction( "${Rest.URL}/poll/addPremadeQuestions", targetType = TargetType.POST ), - title = "Micromata Vorlage nutzen" + title = "poll.button.micromataTemplate" ) ) ) @@ -198,7 +197,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. var processedLayout = LayoutUtils.processEditPage(layout, dto, this) processedLayout.actions.filterIsInstance().find { it.id == "create" - }?.confirmMessage = "Willst du wirklich die Umfrage erstellen? Du kannst die Fragen im Nachhinein nicht mehr bearbeiten." + }?.confirmMessage = "poll.confirmation.creation" return processedLayout } @@ -214,6 +213,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun onAfterSaveOrUpdate(request: HttpServletRequest, poll: PollDO, postData: PostData) { super.onAfterSaveOrUpdate(request, poll, postData) + // todo i18n hier einfügen vielleicht pollMailService.sendMail(subject = "", content = "", to = "test.mail") } @@ -244,10 +244,10 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val userAccess = UILayout.UserAccess(insert = true, update = true) val dto = postData.data - var type = BaseType.valueOf(dto.questionType ?: "TextQuestion") + var type = BaseType.valueOf(dto.questionType ?: "poll.question.textQuestion") var question = Question(uid = UUID.randomUUID().toString(), type = type) if(type == BaseType.SingleResponseQuestion) { - question.answers = mutableListOf("ja", "nein") + question.answers = mutableListOf("yes", "no") } dto.inputFields!!.add(question) @@ -270,11 +270,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. User.restoreDisplayNames(users, userService) val allUsers = dto.attendees?.toMutableList()?: mutableListOf() - var counter = 0 ; + var counter = 0 users?.forEach { user -> - if(allUsers?.filter { it.id == user.id }?.isEmpty() == true) { + if (allUsers?.none { it.id == user.id } == true) { allUsers.add(user) - counter ++ + counter++ } } @@ -287,7 +287,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. .addVariable("ui", createEditLayout(dto, userAccess)) .addVariable("data", dto) ) - } @@ -308,18 +307,17 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } private fun addQuestionFieldset(layout: UILayout, dto: Poll) { dto.inputFields?.forEachIndexed { index, field -> - val objGiven = dto.id != null //differentiates between initial creation and editing val fieldset = UIFieldset(UILength(12), title = field.type.toString()) - if(!objGiven){ + if (!dto.isAlreadyCreated()) { fieldset.add(generateDeleteButton(layout, field.uid)) } - fieldset.add(getUiElement(objGiven, "inputFields[${index}].question", "Frage")) + fieldset.add(getUiElement(dto.isAlreadyCreated(), "inputFields[${index}].question", "Frage")) if (field.type == BaseType.SingleResponseQuestion || field.type == BaseType.MultiResponseQuestion) { field.answers?.forEachIndexed { answerIndex, _ -> - fieldset.add(generateSingleAndMultiResponseAnswer(objGiven, index, field.uid, answerIndex, layout)) + fieldset.add(generateSingleAndMultiResponseAnswer(dto.isAlreadyCreated(), index, field.uid, answerIndex, layout)) } - if(!objGiven) { + if (!dto.isAlreadyCreated()) { fieldset.add( UIRow().add( UIButton.createAddButton( @@ -341,10 +339,10 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. row.add( UICol() .add( - getUiElement(objGiven, "inputFields[${inputFieldIndex}].answers[${answerIndex}]", "Answer ${answerIndex + 1}") + getUiElement(objGiven, "inputFields[${inputFieldIndex}].answers[${answerIndex}]", "poll.answer" + " ${answerIndex + 1}") ) ) - if(!objGiven){ + if (!objGiven) { row.add( UICol() .add( @@ -353,8 +351,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. responseAction = ResponseAction( "${Rest.URL}/poll/deleteAnswer/${questionUid}/${answerIndex}", targetType = TargetType.POST ) - ).withConfirmMessage(layout, confirmMessage = "Willst du wirklich diese Antwortmöglichkeit löschen?"))) - } + ).withConfirmMessage(layout, confirmMessage = "poll.confirmation.deleteAnswer") + ) + ) + } + return row } @@ -389,7 +390,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. responseAction = ResponseAction( "${Rest.URL}/poll/deleteQuestion/${uid}", targetType = TargetType.POST ) - ).withConfirmMessage(layout, confirmMessage = "Willst du wirklich diese Frage löschen?")) + ).withConfirmMessage(layout, confirmMessage = "poll.confirmation.deleteQuestion") + ) ) return row } @@ -427,11 +429,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } return RestUtils.downloadFile(filename, bytes) } - //once created, questions should be ReadOnly - private fun getUiElement(obj: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement{ - if (obj) - return UIReadOnlyField(id, label = label, dataType = dataType) + + // once created, questions should be ReadOnly + private fun getUiElement(obj: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement { + return if (obj) + UIReadOnlyField(id, label = label, dataType = dataType) else - return UIInput(id, label = label, dataType = dataType) + UIInput(id, label = label, dataType = dataType) } } From 3d6b94eb99195335632334b9a29747c6d8a53097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Thu, 11 May 2023 13:26:21 +0200 Subject: [PATCH 085/160] Reformatting for everything --- .../org/projectforge/business/poll/PollDao.kt | 21 +-- .../org/projectforge/rest/poll/CronJobs.kt | 37 ++++-- .../rest/poll/Exel/ExcelExport.kt | 2 +- .../rest/poll/PollInfoPageRest.kt | 122 ++++++++++++------ .../projectforge/rest/poll/PollPageRest.kt | 26 ++-- .../projectforge/rest/poll/types/Question.kt | 5 +- 6 files changed, 137 insertions(+), 76 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index 324042f8f7..d9e9a6f518 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -10,7 +10,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Repository @Repository -open class PollDao : BaseDao(PollDO::class.java){ +open class PollDao : BaseDao(PollDO::class.java) { @Autowired private val groupService: GroupService? = null @@ -30,25 +30,26 @@ open class PollDao : BaseDao(PollDO::class.java){ if (obj == null && operationType == OperationType.SELECT) { return true }; - if (obj != null && operationType == OperationType.SELECT){ - if(hasFullAccess(obj) || isAttendee(obj)) + if (obj != null && operationType == OperationType.SELECT) { + if (hasFullAccess(obj) || isAttendee(obj)) return true } - if(obj != null) { + if (obj != null) { return hasFullAccess(obj) } return false } + fun hasFullAccess(obj: PollDO): Boolean { val loggedInUser = user - if(!obj.fullAccessUserIds.isNullOrBlank() && obj.fullAccessUserIds!!.contains(loggedInUser?.id.toString())) + if (!obj.fullAccessUserIds.isNullOrBlank() && obj.fullAccessUserIds!!.contains(loggedInUser?.id.toString())) return true - if(obj.owner?.id == loggedInUser?.id) + if (obj.owner?.id == loggedInUser?.id) return true - if (!obj.fullAccessGroupIds.isNullOrBlank()){ + if (!obj.fullAccessGroupIds.isNullOrBlank()) { val groupIdArray = obj.fullAccessGroupIds!!.split(", ").map { it.toInt() }.toIntArray() val groupUsers = groupService?.getGroupUsers(groupIdArray) - if(groupUsers?.contains(loggedInUser) == true) + if (groupUsers?.contains(loggedInUser) == true) return true } return false @@ -58,11 +59,11 @@ open class PollDao : BaseDao(PollDO::class.java){ val loggedInUser = user val listOfAttendeesIds = ObjectMapper().readValue(obj.attendeesIds, IntArray::class.java) if (loggedInUser != null) { - if(listOfAttendeesIds.contains(loggedInUser.id)){ + if (listOfAttendeesIds.contains(loggedInUser.id)) { return true } } - obj.attendeesIds= ObjectMapper().writeValueAsString(listOfAttendeesIds) + obj.attendeesIds = ObjectMapper().writeValueAsString(listOfAttendeesIds) return false diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt index eabf3c3bbf..91010a9465 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt @@ -20,6 +20,7 @@ import org.slf4j.LoggerFactory class CronJobs { private val log: Logger = LoggerFactory.getLogger(CronJobs::class.java) + @Autowired private lateinit var pollDao: PollDao @@ -62,8 +63,9 @@ class CronJobs { val attachment = object : MailAttachment { override fun getFilename(): String { - return it.title+ "_" + LocalDateTime.now().year +"_Result"+ ".xlsx" + return it.title + "_" + LocalDateTime.now().year + "_Result" + ".xlsx" } + override fun getContent(): ByteArray? { return exel } @@ -71,7 +73,7 @@ class CronJobs { list.add(attachment) header = "Umfrage ist abgelaufen" - mail =""" + mail = """ Die Umfrage ist zu ende. Hier die ergebnisse. """.trimMargin() @@ -83,22 +85,24 @@ class CronJobs { try { // erstell mir eine funktion, die alles deadlines mir gibt die in der zukunft liegen - val pollsInFuture = polls.filter { it.deadline?.isAfter(LocalDate.now()) ?: false} - pollsInFuture.forEach{ + val pollsInFuture = polls.filter { it.deadline?.isAfter(LocalDate.now()) ?: false } + pollsInFuture.forEach { val daysDifference = ChronoUnit.DAYS.between(LocalDate.now(), it.deadline) - if(daysDifference == 1L || daysDifference == 7L){ + if (daysDifference == 1L || daysDifference == 7L) { header = "Umfrage Endet in $daysDifference Tage" - mail =""" + mail = """ Sehr geehrter Teilnehmer,wir laden Sie herzlich dazu ein, an unserer Umfrage zum Thema ${it.title} teilzunehmen. Ihre Meinung ist uns sehr wichtig und wir würden uns freuen, wenn Sie uns dabei helfen könnten, - unsere Forschungsergebnisse zu verbessern. Für diese Umfrage ist ${it ///owner + unsere Forschungsergebnisse zu verbessern. Für diese Umfrage ist ${ + it ///owner }zuständig. Bei Fragen oder Anmerkungen können Sie sich gerne an ihn wenden. Bitte beachten Sie, dass das Enddatum für die Teilnahme an dieser Umfrage der ${it.deadline.toString()} ist. Wir würden uns freuen, wenn Sie sich die Zeit nehmen könnten, um diese Umfrage auszufüllen. Vielen Dank im Voraus für Ihre Unterstützung. - Mit freundlichen Grüßen,${it ///owmer + Mit freundlichen Grüßen,${ + it ///owmer } """.trimMargin() } @@ -106,10 +110,9 @@ class CronJobs { if (mail.isNotEmpty()) { - pollMailService.sendMail(to="test", subject = header, content = mail, mailAttachments = list) + pollMailService.sendMail(to = "test", subject = header, content = mail, mailAttachments = list) } - } - catch (e:Exception) { + } catch (e: Exception) { log.error(e.toString()) } } @@ -121,9 +124,15 @@ class CronJobs { fun cronDeletePolls() { // check if poll end in future val polls = pollDao.internalLoadAll() - val pollsMoreThanOneYearPast = polls.filter { it.created?.before(Date.from(LocalDate.now().minusYears(1).atStartOfDay( - ZoneId.systemDefault() - ).toInstant())) ?: false } + val pollsMoreThanOneYearPast = polls.filter { + it.created?.before( + Date.from( + LocalDate.now().minusYears(1).atStartOfDay( + ZoneId.systemDefault() + ).toInstant() + ) + ) ?: false + } pollsMoreThanOneYearPast.forEach { pollDao.delete(it) } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt index 651df57b51..f9a79847e6 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt @@ -48,7 +48,6 @@ class ExcelExport { var anzNewRows = 2 anzNewRows += (poll.attendees?.size ?: 0) - createNewRow(excelSheet, emptyRow, anzNewRows) setFirstRow(excelSheet, style, poll) poll.attendees?.sortedBy { it.displayName } @@ -166,6 +165,7 @@ class ExcelExport { ) } } + private fun returnByteFile(excelSheet: ExcelSheet): ByteArray? { excelSheet.excelWorkbook.use { workbook -> val byteArrayOutputStream = workbook.asByteArrayOutputStream diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt index 3fbff669bb..8d6f5f7b07 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt @@ -13,64 +13,104 @@ import javax.servlet.http.HttpServletRequest @RestController @RequestMapping("${Rest.URL}/pollInfo") -class PollInfoPageRest: AbstractDynamicPageRest() { +class PollInfoPageRest : AbstractDynamicPageRest() { - @GetMapping("dynamic") - fun getForm(request: HttpServletRequest): FormLayoutData { + @GetMapping("dynamic") + fun getForm(request: HttpServletRequest): FormLayoutData { - val layout = UILayout("poll.infopage") - val field = UIFieldset() - .add(UILabel(""" Anleitung um eine Umfrage zu erstellen + val layout = UILayout("poll.infopage") + val field = UIFieldset() + .add( + UILabel( + """ Anleitung um eine Umfrage zu erstellen | Als erstes werden die Parameter einer Umfrage angelegt. - """.trimMargin())) - - field.add(UICol() - .add(UIReadOnlyField("title", label = "title", value = "Test"))) - field.add(UICol() - .add(UIReadOnlyField("description", label = "description", value = "description"))) - field.add(UICol() - .add(UIReadOnlyField("location", label = "location", value = "location"))) - field.add(UICol() - .add(UIReadOnlyField("owner", label = "owner", value = "owner"))) - field.add(UICol() - .add(UIReadOnlyField("deadline", label = "deadline", value = "deadline"))) - - field.add(UIRow().add(UICol().add(UILabel("""Anschließend werden die Fragen der Umfrage angelegt. - Die Fragen können aus verschiedenen Typen bestehen. """)))) - - layout.add(field) - - layout.add(UIFieldset().add(UILabel("YesNoQuestion")).add( + """.trimMargin() + ) + ) + + field.add( + UICol() + .add(UIReadOnlyField("title", label = "title", value = "Test")) + ) + field.add( + UICol() + .add(UIReadOnlyField("description", label = "description", value = "description")) + ) + field.add( + UICol() + .add(UIReadOnlyField("location", label = "location", value = "location")) + ) + field.add( + UICol() + .add(UIReadOnlyField("owner", label = "owner", value = "owner")) + ) + field.add( + UICol() + .add(UIReadOnlyField("deadline", label = "deadline", value = "deadline")) + ) + + field.add( + UIRow().add( + UICol().add( + UILabel( + """Anschließend werden die Fragen der Umfrage angelegt. + Die Fragen können aus verschiedenen Typen bestehen. """ + ) + ) + ) + ) + + layout.add(field) + + layout.add( + UIFieldset().add(UILabel("YesNoQuestion")).add( UICol() .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage die mit Ja oder Nein Beantwortet werden kann")) - )) - layout.add(UIFieldset().add(UILabel("MultipleChoiceQuestion")).add( + ) + ) + layout.add( + UIFieldset().add(UILabel("MultipleChoiceQuestion")).add( UICol() .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage die mit mehreren Antworten Beantwortet werden kann")) - )) - layout.add(UIFieldset().add(UILabel("TextQuestion")).add( + ) + ) + layout.add( + UIFieldset().add(UILabel("TextQuestion")).add( UICol() .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage die mit einer Freitext Antwort Beantwortet werden kann")) - )) - layout.add(UIFieldset().add(UILabel("DateQuestion")).add( + ) + ) + layout.add( + UIFieldset().add(UILabel("DateQuestion")).add( UICol() - .add(UIReadOnlyField("question", label = "Question", - value = """Eine Frage ob an einem Tag (Uhrzeit) die Teilnehmer zeit haben. Die einem Ja, Nein oder Vielleicht + .add( + UIReadOnlyField( + "question", label = "Question", + value = """Eine Frage ob an einem Tag (Uhrzeit) die Teilnehmer zeit haben. Die einem Ja, Nein oder Vielleicht Beantwortet werden kann""" - )))) - layout.add(UIFieldset().add(UILabel("Dropdown")).add( + ) + ) + ) + ) + layout.add( + UIFieldset().add(UILabel("Dropdown")).add( UICol() - .add(UIReadOnlyField("question", label = "Question", - value = """ Eine Frage die mit einem Dropdown Beantwortet werden kann""" - )))) + .add( + UIReadOnlyField( + "question", label = "Question", + value = """ Eine Frage die mit einem Dropdown Beantwortet werden kann""" + ) + ) + ) + ) - LayoutUtils.process(layout) + LayoutUtils.process(layout) - return FormLayoutData(null, layout, createServerData(request)) + return FormLayoutData(null, layout, createServerData(request)) - } + } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 1123f13341..44ccbc9417 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -230,8 +230,10 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. found?.answers?.add("") dto.owner = userService.getUser(dto.owner?.id) return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", - createEditLayout(dto, userAccess)) + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable( + "ui", + createEditLayout(dto, userAccess) + ) ) } @@ -246,7 +248,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. var type = BaseType.valueOf(dto.questionType ?: "poll.question.textQuestion") var question = Question(uid = UUID.randomUUID().toString(), type = type) - if(type == BaseType.SingleResponseQuestion) { + if (type == BaseType.SingleResponseQuestion) { question.answers = mutableListOf("yes", "no") } @@ -264,11 +266,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. watchFieldsTriggered: Array? ): ResponseEntity { val userAccess = UILayout.UserAccess() - val groupIds = dto.groupAttendees?.filter{it.id != null}?.map{it.id!!}?.toIntArray() + val groupIds = dto.groupAttendees?.filter { it.id != null }?.map { it.id!! }?.toIntArray() val userIds = UserService().getUserIds(groupService.getGroupUsers(groupIds)) val users = User.toUserList(userIds) User.restoreDisplayNames(users, userService) - val allUsers = dto.attendees?.toMutableList()?: mutableListOf() + val allUsers = dto.attendees?.toMutableList() ?: mutableListOf() var counter = 0 users?.forEach { user -> @@ -282,7 +284,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.attendees = allUsers.sortedBy { it.displayName } return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE + ResponseAction( + targetType = TargetType.UPDATE ) .addVariable("ui", createEditLayout(dto, userAccess)) .addVariable("data", dto) @@ -305,6 +308,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) ) } + private fun addQuestionFieldset(layout: UILayout, dto: Poll) { dto.inputFields?.forEachIndexed { index, field -> val fieldset = UIFieldset(UILength(12), title = field.type.toString()) @@ -334,7 +338,13 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } } - private fun generateSingleAndMultiResponseAnswer(objGiven: Boolean, inputFieldIndex: Int, questionUid: String?, answerIndex: Int, layout: UILayout):UIRow { + private fun generateSingleAndMultiResponseAnswer( + objGiven: Boolean, + inputFieldIndex: Int, + questionUid: String?, + answerIndex: Int, + layout: UILayout + ): UIRow { val row = UIRow() row.add( UICol() @@ -377,7 +387,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } - private fun generateDeleteButton(layout: UILayout, uid:String?):UIRow { + private fun generateDeleteButton(layout: UILayout, uid: String?): UIRow { val row = UIRow() row.add( UICol(UILength(11)) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt index 185eb67171..a30bf4c442 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt @@ -11,10 +11,11 @@ class Question( var parent: String? = null, var isRequired: Boolean? = false, var numberOfSelect: Int? = 1, -){ - fun toObject(string:String): Question { +) { + fun toObject(string: String): Question { return ObjectMapper().readValue(string, Question::class.java) } + fun toJson(): String { return ObjectMapper().writeValueAsString(this) } From bbce1046c97638440afc60d206e9f83c989b51d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Fri, 12 May 2023 14:22:58 +0200 Subject: [PATCH 086/160] Added correct mail text and i18n keys --- .../main/resources/I18nResources.properties | 19 +++++ .../resources/I18nResources_de.properties | 22 ++++- .../org/projectforge/rest/poll/CronJobs.kt | 82 +++++++++---------- .../kotlin/org/projectforge/rest/poll/Poll.kt | 2 +- .../rest/poll/PollInfoPageRest.kt | 24 +++--- .../projectforge/rest/poll/PollPageRest.kt | 35 ++++---- .../rest/poll/ResponsePageRest.kt | 8 +- 7 files changed, 111 insertions(+), 81 deletions(-) diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index be859d9990..dd819c7e81 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -1991,14 +1991,33 @@ poll.fullAccessUsers=Full Access User poll.groupAttendees=Attendee Groups poll.guide=Poll Guide poll.location=Location +poll.infopage=Info Page poll.owner=Owner +poll.question=Question poll.question.textQuestion=Text Question poll.questionType=Question Type poll.response.page=Poll Response Page poll.state=State poll.title=Title +poll.title.list=Polls poll.title.add=Create new Poll +poll.export.response.poll=Export results poll=Poll +poll.mail.endingSoon.header=Poll ending in {0} days +poll.mail.endingSoon.content=

Dear Attendees,

\ +

This is a friendly reminder that the poll "{0}" created by {1} is ending soon, on {2}. Please make sure to submit your responses before the deadline.

\ +

If you have not yet had a chance to participate, please take a few moments to do so before the poll closes. Your input is important and valued.

\ +

{3}

\ +

Thank you for your attention, and have a great day!

\ +

Best regards,

\ +

{1}

+poll.mail.ended.header=Poll ended +poll.mail.ended.content=

Dear Attendees,

\ +

We wanted to let you know that the poll "{0}" created by {1} has now ended. Thank you to everyone who participated and provided valuable input.

\ +

If you missed the deadline to submit your responses, we encourage you to still share your thoughts with us. While we may not be able to include your responses in the official results, your feedback is still valuable for future polls.

\ +

Thank you again for your participation.

\ +

Best regards,

\ +

{1}

projectmanagement.personDays=Person days projectmanagement.personDays.short=pd question.deleteQuestion=Do your really want to delete this object finally? diff --git a/projectforge-business/src/main/resources/I18nResources_de.properties b/projectforge-business/src/main/resources/I18nResources_de.properties index 8704a45753..24f8be685a 100644 --- a/projectforge-business/src/main/resources/I18nResources_de.properties +++ b/projectforge-business/src/main/resources/I18nResources_de.properties @@ -2081,17 +2081,37 @@ poll.error.oneQuestionRequired=Mindestens eine Frage ist erforderlich. poll.fullAccessGroups=Gruppen mit Vollzugriff poll.fullAccessUsers=Benutzer mit Vollzugriff poll.groupAttendees=Teilnehmergruppen +poll.infopage=Infoseite poll.guide=Anleitung poll.location=Ort poll.owner=Eigentümer +poll.question=Frage poll.question.textQuestion=Textfrage poll.questionType=Fragen Typ poll.response.page=Seite zur Umfrageantwort poll.state=Status poll.title=Titel +poll.title.list=Umfragen poll.title.add=Neue Umfrage erstellen +poll.export.response.poll=Ergebnisse exportieren poll=Umfrage -poll.mail. +poll.mail.endingSoon.header=Umfrage endet in {0} Tagen +poll.mail.endingSoon.content=

Liebe Teilnehmerinnen und Teilnehmer,

\ +

wir möchten Sie daran erinnern, dass die Umfrage "{0}", erstellt von {1}, bald endet, nämlich am {2}. Bitte achten Sie darauf, Ihre Antworten vor dem Ablaufdatum einzureichen.

\ +

Falls Sie noch nicht die Gelegenheit hatten, an der Umfrage teilzunehmen, nehmen Sie sich bitte einen Moment Zeit, um dies zu tun, bevor die Umfrage geschlossen wird. Ihre Meinung ist wichtig und wertvoll.

\ +

{3}

\ +

Vielen Dank für Ihre Aufmerksamkeit und einen schönen Tag!

\ +
\ +

Freundliche Grüße,

\ +

{1}

+poll.mail.ended.header=Umfrage beendet +poll.mail.ended.content=

Liebe Teilnehmerinnen und Teilnehmer,

\ +

wir möchten Sie darüber informieren, dass die Umfrage "{0}", erstellt von {1}, nun abgeschlossen ist. Vielen Dank an alle, die teilgenommen und wertvolles Feedback gegeben haben.

\ +

Falls Sie den Einsendeschluss verpasst haben, möchten wir Sie dennoch ermutigen, uns Ihre Gedanken mitzuteilen. Auch wenn wir Ihre Antworten möglicherweise nicht in den offiziellen Ergebnissen berücksichtigen können, ist Ihr Feedback dennoch wertvoll für zukünftige Umfragen und Initiativen.

\ +

Nochmals vielen Dank für Ihre Teilnahme.

\ +
\ +

Freundliche Grüße,

\ +

{1}

projectmanagement.personDays=Personentage projectmanagement.personDays.short=PT question.deleteQuestion=Soll das Objekt wirklich unwiderruflich gelöscht werden? diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt index 91010a9465..b58c75ff0c 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt @@ -2,6 +2,7 @@ package org.projectforge.rest.poll import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao +import org.projectforge.framework.i18n.translateMsg import org.projectforge.mail.MailAttachment import org.projectforge.rest.poll.Exel.ExcelExport import org.springframework.beans.factory.annotation.Autowired @@ -44,13 +45,14 @@ class CronJobs { /** * Method to end polls after deadline */ - fun cronEndPolls() { - var mail = ""; - var header = ""; + private fun cronEndPolls() { + var mailContent = "" + var mailHeader = "" + val mailAttachments = ArrayList() + val mailTo = "l.spohr@micromata.de" val polls = pollDao.internalLoadAll() - val list = ArrayList() - // set State.FINISHED for all old polls + // set State.FINISHED for all old polls and export excel polls.forEach { if (it.deadline?.isBefore(LocalDate.now()) == true) { it.state = PollDO.State.FINISHED @@ -58,70 +60,60 @@ class CronJobs { val poll = Poll() poll.copyFrom(it) - val exel = exporter - .getExcel(poll) + val excel = exporter.getExcel(poll) - val attachment = object : MailAttachment { + val mailAttachment = object : MailAttachment { override fun getFilename(): String { - return it.title + "_" + LocalDateTime.now().year + "_Result" + ".xlsx" + return "${it.title}_${LocalDateTime.now().year}_Result.xlsx" } override fun getContent(): ByteArray? { - return exel + return excel } } - list.add(attachment) + mailAttachments.add(mailAttachment) - header = "Umfrage ist abgelaufen" - mail = """ - Die Umfrage ist zu ende. Hier die ergebnisse. - """.trimMargin() + mailHeader = translateMsg("poll.mail.ended.header") + mailContent = translateMsg( + "poll.mail.ended.content", it.title, it.owner?.displayName + ) pollDao.internalSaveOrUpdate(it) - } + sendMail(mailTo, mailContent, mailHeader, mailAttachments) + } } - try { - - // erstell mir eine funktion, die alles deadlines mir gibt die in der zukunft liegen - val pollsInFuture = polls.filter { it.deadline?.isAfter(LocalDate.now()) ?: false } - pollsInFuture.forEach { - val daysDifference = ChronoUnit.DAYS.between(LocalDate.now(), it.deadline) - if (daysDifference == 1L || daysDifference == 7L) { - header = "Umfrage Endet in $daysDifference Tage" - mail = """ - Sehr geehrter Teilnehmer,wir laden Sie herzlich dazu ein, an unserer Umfrage zum Thema ${it.title} teilzunehmen. - Ihre Meinung ist uns sehr wichtig und wir würden uns freuen, wenn Sie uns dabei helfen könnten, - unsere Forschungsergebnisse zu verbessern. Für diese Umfrage ist ${ - it ///owner - }zuständig. - Bei Fragen oder Anmerkungen können Sie sich gerne an ihn wenden. - Bitte beachten Sie, dass das Enddatum für die Teilnahme an dieser Umfrage der ${it.deadline.toString()} ist. - Wir würden uns freuen, wenn Sie sich die Zeit nehmen könnten, um diese Umfrage auszufüllen. - Vielen Dank im Voraus für Ihre Unterstützung. - - Mit freundlichen Grüßen,${ - it ///owmer - } - """.trimMargin() - } + val pollsInFuture = polls.filter { it.deadline?.isAfter(LocalDate.now()) ?: false } + pollsInFuture.forEach { + val daysDifference = ChronoUnit.DAYS.between(LocalDate.now(), it.deadline) + if (daysDifference == 1L || daysDifference == 7L) { + mailHeader = translateMsg("poll.mail.endingSoon.header", daysDifference) + mailContent = translateMsg( + "poll.mail.endingSoon.content", + it.title, + it.owner?.displayName, + it.deadline.toString(), + "https://projectforge.micromata.de/react/response/dynamic/${it.id}" + ) } + } + } - - if (mail.isNotEmpty()) { - pollMailService.sendMail(to = "test", subject = header, content = mail, mailAttachments = list) + private fun sendMail(to: String, subject: String, content: String, mailAttachments: List? = null) { + try { + if (content.isNotEmpty()) { + pollMailService.sendMail(subject = subject, content = content, mailAttachments = mailAttachments, to = to) } } catch (e: Exception) { log.error(e.toString()) } } - /** * Method to delete old polls */ - fun cronDeletePolls() { + private fun cronDeletePolls() { // check if poll end in future val polls = pollDao.internalLoadAll() val pollsMoreThanOneYearPast = polls.filter { diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index 7433c816c3..7d0a06cc85 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -42,7 +42,7 @@ class Poll( dest.fullAccessUserIds = User.toIntList(fullAccessUsers) dest.groupAttendeesIds = Group.toIntList(groupAttendees) dest.attendeesIds = User.toIntList(attendees) - if(inputFields!= null){ + if (inputFields != null) { dest.inputFields = ObjectMapper().writeValueAsString(inputFields) } } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt index 8d6f5f7b07..b74642c445 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt @@ -2,7 +2,6 @@ package org.projectforge.rest.poll import org.projectforge.rest.config.Rest import org.projectforge.rest.core.AbstractDynamicPageRest -import org.projectforge.rest.core.PagesResolver import org.projectforge.rest.dto.FormLayoutData import org.projectforge.ui.* import org.springframework.web.bind.annotation.GetMapping @@ -22,7 +21,7 @@ class PollInfoPageRest : AbstractDynamicPageRest() { val field = UIFieldset() .add( UILabel( - """ Anleitung um eine Umfrage zu erstellen + """ Anleitung, um eine Umfrage zu erstellen | Als erstes werden die Parameter einer Umfrage angelegt. """.trimMargin() ) @@ -65,19 +64,25 @@ class PollInfoPageRest : AbstractDynamicPageRest() { layout.add( UIFieldset().add(UILabel("YesNoQuestion")).add( UICol() - .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage die mit Ja oder Nein Beantwortet werden kann")) + .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage, die mit Ja oder Nein beantwortet werden kann")) ) ) layout.add( UIFieldset().add(UILabel("MultipleChoiceQuestion")).add( UICol() - .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage die mit mehreren Antworten Beantwortet werden kann")) + .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage, die mit mehreren Antworten beantwortet werden kann")) ) ) layout.add( UIFieldset().add(UILabel("TextQuestion")).add( UICol() - .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage die mit einer Freitext Antwort Beantwortet werden kann")) + .add( + UIReadOnlyField( + "question", + label = "Question", + value = "Eine Frage, die mit einer Freitext Antwort beantwortet werden kann" + ) + ) ) ) @@ -87,8 +92,8 @@ class PollInfoPageRest : AbstractDynamicPageRest() { .add( UIReadOnlyField( "question", label = "Question", - value = """Eine Frage ob an einem Tag (Uhrzeit) die Teilnehmer zeit haben. Die einem Ja, Nein oder Vielleicht - Beantwortet werden kann""" + value = """Eine Frage, ob an einem Tag (Uhrzeit) die Teilnehmer Zeit haben. Die mit einem Ja, Nein oder Vielleicht + beantwortet werden kann""" ) ) ) @@ -99,7 +104,7 @@ class PollInfoPageRest : AbstractDynamicPageRest() { .add( UIReadOnlyField( "question", label = "Question", - value = """ Eine Frage die mit einem Dropdown Beantwortet werden kann""" + value = """ Eine Frage, die mit einem Dropdown beantwortet werden kann""" ) ) ) @@ -109,8 +114,5 @@ class PollInfoPageRest : AbstractDynamicPageRest() { LayoutUtils.process(layout) return FormLayoutData(null, layout, createServerData(request)) - - } - } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 44ccbc9417..86b7d8f871 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -4,9 +4,9 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.projectforge.business.group.service.GroupService import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao -import org.projectforge.business.poll.PollResponseDao import org.projectforge.business.user.service.UserService import org.projectforge.framework.access.AccessException +import org.projectforge.framework.i18n.translateMsg import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.rest.config.Rest @@ -35,10 +35,10 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. private val log: Logger = LoggerFactory.getLogger(PollPageRest::class.java) @Autowired - private lateinit var userService: UserService; + private lateinit var userService: UserService @Autowired - private lateinit var groupService: GroupService; + private lateinit var groupService: GroupService @Autowired private lateinit var pollMailService: PollMailService @@ -46,9 +46,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @Autowired private lateinit var pollDao: PollDao - @Autowired - private lateinit var pollResponseDao: PollResponseDao - @Autowired private lateinit var excelExport: ExcelExport @@ -141,7 +138,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. .add(UISelect.createGroupSelect(lc, "fullAccessGroups", true, "poll.fullAccessGroups")) .add(UISelect.createUserSelect(lc, "attendees", true, "poll.attendees")) .add(UISelect.createGroupSelect(lc, "groupAttendees", true, "poll.groupAttendees")) - if (dto.isAlreadyCreated() == false) { + if (!dto.isAlreadyCreated()) { fieldset.add( UIRow() .add( @@ -194,7 +191,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. layout.watchFields.addAll(listOf("groupAttendees")) - var processedLayout = LayoutUtils.processEditPage(layout, dto, this) + val processedLayout = LayoutUtils.processEditPage(layout, dto, this) processedLayout.actions.filterIsInstance().find { it.id == "create" }?.confirmMessage = "poll.confirmation.creation" @@ -246,8 +243,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val userAccess = UILayout.UserAccess(insert = true, update = true) val dto = postData.data - var type = BaseType.valueOf(dto.questionType ?: "poll.question.textQuestion") - var question = Question(uid = UUID.randomUUID().toString(), type = type) + val type = BaseType.valueOf(dto.questionType ?: "poll.question.textQuestion") + val question = Question(uid = UUID.randomUUID().toString(), type = type) if (type == BaseType.SingleResponseQuestion) { question.answers = mutableListOf("yes", "no") } @@ -274,7 +271,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. var counter = 0 users?.forEach { user -> - if (allUsers?.none { it.id == user.id } == true) { + if (allUsers.none { it.id == user.id }) { allUsers.add(user) counter++ } @@ -315,7 +312,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. if (!dto.isAlreadyCreated()) { fieldset.add(generateDeleteButton(layout, field.uid)) } - fieldset.add(getUiElement(dto.isAlreadyCreated(), "inputFields[${index}].question", "Frage")) + fieldset.add(getUiElement(dto.isAlreadyCreated(), "inputFields[${index}].question", translateMsg("poll.question"))) if (field.type == BaseType.SingleResponseQuestion || field.type == BaseType.MultiResponseQuestion) { field.answers?.forEachIndexed { answerIndex, _ -> @@ -349,7 +346,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. row.add( UICol() .add( - getUiElement(objGiven, "inputFields[${inputFieldIndex}].answers[${answerIndex}]", "poll.answer" + " ${answerIndex + 1}") + getUiElement( + objGiven, + "inputFields[${inputFieldIndex}].answers[${answerIndex}]", + translateMsg("poll.answer") + " ${answerIndex + 1}" + ) ) ) if (!objGiven) { @@ -424,18 +425,18 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } @PostMapping("/export/{id}") - fun export(request: HttpServletRequest ,@PathVariable("id") id: String) : ResponseEntity? { + fun export(@PathVariable("id") id: String) : ResponseEntity? { val poll = Poll() val pollDo = pollDao.getById(id.toInt()) poll.copyFrom(pollDo) User.restoreDisplayNames(poll.attendees, userService) val bytes: ByteArray? = excelExport .getExcel(poll) - val filename = ( poll.title+ "_" + LocalDateTime.now().year +"_Result"+ ".xlsx") + val filename = ( "${poll.title}_${LocalDateTime.now().year}_Result.xlsx") - if (bytes == null || bytes.size == 0) { + if (bytes == null || bytes.isEmpty()) { log.error("Oops, xlsx has zero size. Filename: $filename") - return null; + return null } return RestUtils.downloadFile(filename, bytes) } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index e16b4e4305..4d048cab19 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -5,7 +5,6 @@ import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao import org.projectforge.business.poll.PollResponseDO import org.projectforge.business.poll.PollResponseDao -import org.projectforge.framework.access.AccessChecker import org.projectforge.framework.access.AccessCheckerImpl.I18N_KEY_VIOLATION_USER_NOT_MEMBER_OF import org.projectforge.framework.access.AccessException import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext @@ -34,9 +33,6 @@ class ResponsePageRest : AbstractDynamicPageRest() { @Autowired private lateinit var pollResponseDao: PollResponseDao - @Autowired - private lateinit var accesscheck: AccessChecker - @GetMapping("dynamic") fun getForm(request: HttpServletRequest, @RequestParam("id") pollStringId: String?): FormLayoutData { val id = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") @@ -44,7 +40,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { val pollDto = transformPollFromDB(pollData) if (pollDto.state == PollDO.State.FINISHED) { - throw AccessException(I18N_KEY_VIOLATION_USER_NOT_MEMBER_OF, "Umfrage wurde bereits beendet"); + throw AccessException(I18N_KEY_VIOLATION_USER_NOT_MEMBER_OF, "Umfrage wurde bereits beendet") } val layout = UILayout("poll.response.title") @@ -102,7 +98,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { } if (field.type == BaseType.MultiResponseQuestion) { field.answers?.forEachIndexed { index2, _ -> - if(pollResponse.responses?.get(index)?.answers?.getOrNull(index2) == null){ + if (pollResponse.responses?.get(index)?.answers?.getOrNull(index2) == null) { pollResponse.responses?.get(index)?.answers?.add(index2, false) } col.add(UICheckbox("responses[$index].answers[$index2]", label = field.answers?.get(index2) ?: "")) From 4463f2f7bb4c864e7db501590ace49df28960ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 16 May 2023 08:46:46 +0200 Subject: [PATCH 087/160] Refactoring --- .../org/projectforge/business/poll/PollDO.kt | 2 - .../org/projectforge/rest/poll/CronJobs.kt | 2 +- .../rest/poll/Detail/View/PollDetailRest.kt | 17 ----- .../projectforge/rest/poll/PollMailService.kt | 6 +- .../projectforge/rest/poll/PollPageRest.kt | 12 ++-- .../rest/poll/{Exel => excel}/ExcelExport.kt | 62 +++++++++---------- 6 files changed, 38 insertions(+), 63 deletions(-) delete mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Detail/View/PollDetailRest.kt rename projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/{Exel => excel}/ExcelExport.kt (75%) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 17f88704b2..31baa6a0ca 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -1,8 +1,6 @@ package org.projectforge.business.poll import org.hibernate.search.annotations.Indexed -import org.hibernate.search.annotations.IndexedEmbedded -import org.projectforge.business.common.BaseUserGroupRightsDO import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.persistence.api.AUserRightId import org.projectforge.framework.persistence.entities.DefaultBaseDO diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt index b58c75ff0c..e6985fe7bb 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt @@ -4,7 +4,7 @@ import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao import org.projectforge.framework.i18n.translateMsg import org.projectforge.mail.MailAttachment -import org.projectforge.rest.poll.Exel.ExcelExport +import org.projectforge.rest.poll.excel.ExcelExport import org.springframework.beans.factory.annotation.Autowired import java.util.* import org.springframework.scheduling.annotation.Scheduled diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Detail/View/PollDetailRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Detail/View/PollDetailRest.kt deleted file mode 100644 index ffbd65990c..0000000000 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Detail/View/PollDetailRest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.projectforge.rest.poll.Detail.View - -import org.projectforge.rest.config.Rest -import org.projectforge.rest.config.RestUtils -import org.projectforge.rest.core.AbstractDynamicPageRest -import org.projectforge.rest.poll.Exel.ExcelExport -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController -import javax.servlet.http.HttpServletRequest - -class PollDetailRest : AbstractDynamicPageRest(){ - - -} diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt index 83b382e730..7127c8996b 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt @@ -13,7 +13,7 @@ class PollMailService { private lateinit var sendMail: SendMail - fun sendMail(to:String ,subject: String,content: String, mailAttachments: List?= null){ + fun sendMail(to: String, subject: String, content: String, mailAttachments: List? = null) { val mail = Mail() mail.subject = subject mail.contentType = Mail.CONTENTTYPE_HTML @@ -23,8 +23,4 @@ class PollMailService { } - - - - } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 86b7d8f871..1b569094f6 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -13,7 +13,7 @@ import org.projectforge.rest.config.Rest import org.projectforge.rest.config.RestUtils import org.projectforge.rest.core.* import org.projectforge.rest.dto.* -import org.projectforge.rest.poll.Exel.ExcelExport +import org.projectforge.rest.poll.excel.ExcelExport import org.projectforge.rest.poll.types.BaseType import org.projectforge.rest.poll.types.PREMADE_QUESTIONS import org.projectforge.rest.poll.types.Question @@ -103,11 +103,13 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ), title = "poll.response.page" ) - ).add(UIButton.createExportButton( + ).add( + UIButton.createExportButton( id = "export-poll-response-button", responseAction = ResponseAction("${Rest.URL}/poll/export/${dto.id}", targetType = TargetType.POST), title = "poll.export.response.poll" - )) + ) + ) } fieldset.add( UIRow().add( @@ -425,14 +427,14 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } @PostMapping("/export/{id}") - fun export(@PathVariable("id") id: String) : ResponseEntity? { + fun export(@PathVariable("id") id: String): ResponseEntity? { val poll = Poll() val pollDo = pollDao.getById(id.toInt()) poll.copyFrom(pollDo) User.restoreDisplayNames(poll.attendees, userService) val bytes: ByteArray? = excelExport .getExcel(poll) - val filename = ( "${poll.title}_${LocalDateTime.now().year}_Result.xlsx") + val filename = ("${poll.title}_${LocalDateTime.now().year}_Result.xlsx") if (bytes == null || bytes.isEmpty()) { log.error("Oops, xlsx has zero size. Filename: $filename") diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt similarity index 75% rename from projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt rename to projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt index f9a79847e6..e010a7ecbc 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Exel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt @@ -1,4 +1,4 @@ -package org.projectforge.rest.poll.Exel +package org.projectforge.rest.poll.excel import de.micromata.merlin.excel.ExcelRow import de.micromata.merlin.excel.ExcelSheet @@ -6,7 +6,6 @@ import de.micromata.merlin.excel.ExcelWorkbook import org.apache.poi.ss.usermodel.CellStyle import org.apache.poi.ss.usermodel.HorizontalAlignment import org.apache.poi.ss.util.CellRangeAddress -import org.projectforge.business.poll.PollDao import org.projectforge.business.poll.PollResponseDao import org.projectforge.rest.dto.User import org.projectforge.rest.poll.Poll @@ -30,9 +29,6 @@ class ExcelExport { @Autowired private lateinit var pollResponseDao: PollResponseDao - @Autowired - private lateinit var pollDao: PollDao - fun getExcel(poll: Poll): ByteArray? { val responses = pollResponseDao.internalLoadAll().filter { it.poll?.id == poll.id } @@ -54,7 +50,7 @@ class ExcelExport { poll.attendees?.forEachIndexed { index, user -> val res = PollResponse() responses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } - setNewRows(excelSheet,poll, user, res, index) + setNewRows(excelSheet, poll, user, res, index) } return returnByteFile(excelSheet) } @@ -66,16 +62,17 @@ class ExcelExport { return null } - private fun setFirstRow(excelSheet: ExcelSheet,style: CellStyle,poll: Poll){ + private fun setFirstRow(excelSheet: ExcelSheet, style: CellStyle, poll: Poll) { val excelRow = excelSheet.getRow(0) val excelRow1 = excelSheet.getRow(1) style.alignment = HorizontalAlignment.CENTER var merge = 1 - poll.inputFields?.forEach{question -> - if(question.type==BaseType.MultiResponseQuestion || - question.type==BaseType.SingleResponseQuestion ) { + poll.inputFields?.forEach { question -> + if (question.type == BaseType.MultiResponseQuestion || + question.type == BaseType.SingleResponseQuestion + ) { var counter = merge question.answers?.forEach { answer -> excelRow1.getCell(counter).setCellValue(answer) @@ -87,10 +84,9 @@ class ExcelExport { excelSheet.autosize(merge) // cuter -1 because the counter-- - excelSheet.addMergeRegion(CellRangeAddress(0,0,merge,counter)) + excelSheet.addMergeRegion(CellRangeAddress(0, 0, merge, counter)) merge = counter - } - else { + } else { excelRow.getCell(merge).setCellValue(question.question) excelRow.setCellStyle(style) excelSheet.autosize(merge) @@ -99,42 +95,41 @@ class ExcelExport { } excelRow.setHeight(30F) } - private fun setNewRows(excelSheet: ExcelSheet, poll:Poll, user: User, res:PollResponse?, index: Int) { + private fun setNewRows(excelSheet: ExcelSheet, poll: Poll, user: User, res: PollResponse?, index: Int) { val excelRow = excelSheet.getRow(FIRST_DATA_ROW_NUM + index) excelRow.getCell(0).setCellValue(user.displayName) excelSheet.autosize(0) - var CELL=0 + var cell = 0 - var largestAwsnser=""; - poll.inputFields?.forEachIndexed{i, question -> + var largestAnswer = "" + poll.inputFields?.forEachIndexed { _, question -> val questionAnswer = res?.responses?.find { it.questionUid == question.uid } - if (questionAnswer?.answers.isNullOrEmpty()){ - CELL += question.answers?.size?:0 + if (questionAnswer?.answers.isNullOrEmpty()) { + cell += question.answers?.size ?: 0 } questionAnswer?.answers?.forEachIndexed { ind, antwort -> - CELL++ - if (question.type == BaseType.SingleResponseQuestion || question.type == BaseType.MultiResponseQuestion){ - if(antwort is Boolean && antwort == true){ - excelRow.getCell(CELL).setCellValue("X") + cell++ + if (question.type == BaseType.SingleResponseQuestion || question.type == BaseType.MultiResponseQuestion) { + if (antwort is Boolean && antwort == true) { + excelRow.getCell(cell).setCellValue("X") } - if(antwort is String && antwort.equals(question.answers?.get(ind))){ - excelRow.getCell(CELL).setCellValue("X") + if (antwort is String && antwort == question.answers?.get(ind)) { + excelRow.getCell(cell).setCellValue("X") } - } - else{ - excelRow.getCell(CELL).setCellValue(antwort.toString()) - if(countLines(antwort.toString()) > countLines(largestAwsnser)){ - largestAwsnser = antwort.toString() + } else { + excelRow.getCell(cell).setCellValue(antwort.toString()) + if (countLines(antwort.toString()) > countLines(largestAnswer)) { + largestAnswer = antwort.toString() } } - excelSheet.autosize(CELL) + excelSheet.autosize(cell) } } - val puffer: String = largestAwsnser + val puffer: String = largestAnswer var counterOfBreaking = 0 var counterOfOverlength = 0 @@ -142,11 +137,12 @@ class ExcelExport { // check for line-breaks for (i in pufferSplit.indices) { counterOfBreaking++ - counterOfOverlength += pufferSplit.get(i).length / 70 + counterOfOverlength += pufferSplit[i].length / 70 } excelRow.setHeight((14 + counterOfOverlength * 14 + counterOfBreaking * 14).toFloat()) //excelRow.setHeight(20F) ///TODO LEON FIX THIS PROBLEM } + private fun countLines(str: String): Int { val lines = str.split("\r\n|\r|\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() return lines.size From 369c0c552d728efa50c24df5c5bae30160a8db8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 16 May 2023 11:24:11 +0200 Subject: [PATCH 088/160] Added more mailing --- .../main/resources/I18nResources.properties | 11 ++- .../resources/I18nResources_de.properties | 11 ++- .../poll/{CronJobs.kt => PollCronJobs.kt} | 75 ++++++++----------- .../projectforge/rest/poll/PollMailService.kt | 24 ++++-- .../projectforge/rest/poll/PollPageRest.kt | 29 +++++-- .../rest/poll/excel/ExcelExport.kt | 2 - 6 files changed, 91 insertions(+), 61 deletions(-) rename projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/{CronJobs.kt => PollCronJobs.kt} (56%) diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index dd819c7e81..67e0403b0d 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -2003,7 +2003,7 @@ poll.title.list=Polls poll.title.add=Create new Poll poll.export.response.poll=Export results poll=Poll -poll.mail.endingSoon.header=Poll ending in {0} days +poll.mail.endingSoon.subject=Poll ending in {0} days poll.mail.endingSoon.content=

Dear Attendees,

\

This is a friendly reminder that the poll "{0}" created by {1} is ending soon, on {2}. Please make sure to submit your responses before the deadline.

\

If you have not yet had a chance to participate, please take a few moments to do so before the poll closes. Your input is important and valued.

\ @@ -2011,13 +2011,20 @@ poll.mail.endingSoon.content=

Dear Attendees,

\

Thank you for your attention, and have a great day!

\

Best regards,

\

{1}

-poll.mail.ended.header=Poll ended +poll.mail.ended.subject=Poll ended poll.mail.ended.content=

Dear Attendees,

\

We wanted to let you know that the poll "{0}" created by {1} has now ended. Thank you to everyone who participated and provided valuable input.

\

If you missed the deadline to submit your responses, we encourage you to still share your thoughts with us. While we may not be able to include your responses in the official results, your feedback is still valuable for future polls.

\

Thank you again for your participation.

\

Best regards,

\

{1}

+poll.mail.update.subject=Poll was edited +poll.mail.update.content=

Liebe Teilnehmerinnen und Teilnehmer,

\ +

We wanted to let you know that the poll "{0}" was edited recently.

\ +

If you already submitted your answers, you should check, if there were any major changes made.

\ +

Thank you again for your participation.

\ +

Best regards,

\ +

{1}

projectmanagement.personDays=Person days projectmanagement.personDays.short=pd question.deleteQuestion=Do your really want to delete this object finally? diff --git a/projectforge-business/src/main/resources/I18nResources_de.properties b/projectforge-business/src/main/resources/I18nResources_de.properties index 24f8be685a..338d7432f1 100644 --- a/projectforge-business/src/main/resources/I18nResources_de.properties +++ b/projectforge-business/src/main/resources/I18nResources_de.properties @@ -2095,7 +2095,7 @@ poll.title.list=Umfragen poll.title.add=Neue Umfrage erstellen poll.export.response.poll=Ergebnisse exportieren poll=Umfrage -poll.mail.endingSoon.header=Umfrage endet in {0} Tagen +poll.mail.endingSoon.subject=Umfrage endet in {0} Tagen poll.mail.endingSoon.content=

Liebe Teilnehmerinnen und Teilnehmer,

\

wir möchten Sie daran erinnern, dass die Umfrage "{0}", erstellt von {1}, bald endet, nämlich am {2}. Bitte achten Sie darauf, Ihre Antworten vor dem Ablaufdatum einzureichen.

\

Falls Sie noch nicht die Gelegenheit hatten, an der Umfrage teilzunehmen, nehmen Sie sich bitte einen Moment Zeit, um dies zu tun, bevor die Umfrage geschlossen wird. Ihre Meinung ist wichtig und wertvoll.

\ @@ -2104,7 +2104,7 @@ poll.mail.endingSoon.content=

Liebe Teilnehmerinnen und Teilnehmer,

\
\

Freundliche Grüße,

\

{1}

-poll.mail.ended.header=Umfrage beendet +poll.mail.ended.subject=Umfrage beendet poll.mail.ended.content=

Liebe Teilnehmerinnen und Teilnehmer,

\

wir möchten Sie darüber informieren, dass die Umfrage "{0}", erstellt von {1}, nun abgeschlossen ist. Vielen Dank an alle, die teilgenommen und wertvolles Feedback gegeben haben.

\

Falls Sie den Einsendeschluss verpasst haben, möchten wir Sie dennoch ermutigen, uns Ihre Gedanken mitzuteilen. Auch wenn wir Ihre Antworten möglicherweise nicht in den offiziellen Ergebnissen berücksichtigen können, ist Ihr Feedback dennoch wertvoll für zukünftige Umfragen und Initiativen.

\ @@ -2112,6 +2112,13 @@ poll.mail.ended.content=

Liebe Teilnehmerinnen und Teilnehmer,

\
\

Freundliche Grüße,

\

{1}

+poll.mail.update.subject=Umfrage wurde bearbeitet +poll.mail.update.content=

Liebe Teilnehmerinnen und Teilnehmer,

\ +

Wir möchten Ihnen mitteilen, dass die Umfrage "{0}" kürzlich bearbeitet wurde.

\ +

Falls Sie bereits Ihre Antworten eingereicht haben, sollten Sie überprüfen, ob wesentliche Änderungen vorgenommen wurden.

\ +

Nochmals vielen Dank für Ihre Teilnahme.

\ +

Freundliche Grüße,

\ +

{1}

projectmanagement.personDays=Personentage projectmanagement.personDays.short=PT question.deleteQuestion=Soll das Objekt wirklich unwiderruflich gelöscht werden? diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt similarity index 56% rename from projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt rename to projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt index e6985fe7bb..f02fb6d84a 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt @@ -12,15 +12,13 @@ import org.springframework.web.bind.annotation.RestController import java.time.LocalDate import java.time.LocalDateTime import java.time.ZoneId +import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit import kotlin.collections.ArrayList -import org.slf4j.Logger -import org.slf4j.LoggerFactory @RestController -class CronJobs { +class PollCronJobs { - private val log: Logger = LoggerFactory.getLogger(CronJobs::class.java) @Autowired private lateinit var pollDao: PollDao @@ -34,7 +32,7 @@ class CronJobs { /** * Cron job for daily stuff */ -// @Scheduled(cron = "0 0 1 * * *") // 1am everyday +// todo @Scheduled(cron = "0 0 1 * * *") // 1am everyday @Scheduled(cron = "0 * * * * *") // 1am everyday fun dailyCronJobs() { cronDeletePolls() @@ -46,69 +44,60 @@ class CronJobs { * Method to end polls after deadline */ private fun cronEndPolls() { - var mailContent = "" - var mailHeader = "" - val mailAttachments = ArrayList() - val mailTo = "l.spohr@micromata.de" - val polls = pollDao.internalLoadAll() + val pollDOs = pollDao.internalLoadAll() // set State.FINISHED for all old polls and export excel - polls.forEach { - if (it.deadline?.isBefore(LocalDate.now()) == true) { - it.state = PollDO.State.FINISHED + pollDOs.forEach { pollDO -> + if (pollDO.deadline?.isBefore(LocalDate.now()) == true) { + pollDO.state = PollDO.State.FINISHED val poll = Poll() - poll.copyFrom(it) + poll.copyFrom(pollDO) val excel = exporter.getExcel(poll) val mailAttachment = object : MailAttachment { override fun getFilename(): String { - return "${it.title}_${LocalDateTime.now().year}_Result.xlsx" + return "${pollDO.title}_${LocalDateTime.now().year}_Result.xlsx" } override fun getContent(): ByteArray? { return excel } } - mailAttachments.add(mailAttachment) - - mailHeader = translateMsg("poll.mail.ended.header") - mailContent = translateMsg( - "poll.mail.ended.content", it.title, it.owner?.displayName - ) - - pollDao.internalSaveOrUpdate(it) - - sendMail(mailTo, mailContent, mailHeader, mailAttachments) + // add all attendees mails + var mailTo: ArrayList = poll.attendees?.map { it.email } as ArrayList + val mailFrom = pollDO.owner?.email.toString() + val mailSubject = translateMsg("poll.mail.ended.subject") + val mailContent = translateMsg("poll.mail.ended.content", pollDO.title, pollDO.owner?.displayName) + + pollDao.internalSaveOrUpdate(pollDO) + pollMailService.sendMail(mailFrom, mailTo, mailContent, mailSubject, listOf(mailAttachment)) } } - val pollsInFuture = polls.filter { it.deadline?.isAfter(LocalDate.now()) ?: false } - pollsInFuture.forEach { - val daysDifference = ChronoUnit.DAYS.between(LocalDate.now(), it.deadline) + val pollsInFuture = pollDOs.filter { it.deadline?.isAfter(LocalDate.now()) ?: false } + pollsInFuture.forEach { pollDO -> + val poll = Poll() + poll.copyFrom(pollDO) + val daysDifference = ChronoUnit.DAYS.between(LocalDate.now(), pollDO.deadline) if (daysDifference == 1L || daysDifference == 7L) { - mailHeader = translateMsg("poll.mail.endingSoon.header", daysDifference) - mailContent = translateMsg( + // add all attendees mails + var mailTo: ArrayList = poll.attendees?.map { it.email } as ArrayList + val mailFrom = pollDO.owner?.email.toString() + val mailSubject = translateMsg("poll.mail.endingSoon.subject", daysDifference) + val mailContent = translateMsg( "poll.mail.endingSoon.content", - it.title, - it.owner?.displayName, - it.deadline.toString(), - "https://projectforge.micromata.de/react/response/dynamic/${it.id}" + pollDO.title, + pollDO.owner?.displayName, + pollDO.deadline?.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")).toString(), + "https://projectforge.micromata.de/react/response/dynamic/${pollDO.id}" ) + pollMailService.sendMail(mailFrom, mailTo, mailSubject, mailContent) } } } - private fun sendMail(to: String, subject: String, content: String, mailAttachments: List? = null) { - try { - if (content.isNotEmpty()) { - pollMailService.sendMail(subject = subject, content = content, mailAttachments = mailAttachments, to = to) - } - } catch (e: Exception) { - log.error(e.toString()) - } - } /** * Method to delete old polls diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt index 7127c8996b..733f5d9cef 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt @@ -5,6 +5,8 @@ import org.projectforge.mail.MailAttachment import org.projectforge.mail.SendMail import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service +import org.slf4j.Logger +import org.slf4j.LoggerFactory @Service class PollMailService { @@ -12,14 +14,22 @@ class PollMailService { @Autowired private lateinit var sendMail: SendMail + private val log: Logger = LoggerFactory.getLogger(PollMailService::class.java) - fun sendMail(to: String, subject: String, content: String, mailAttachments: List? = null) { - val mail = Mail() - mail.subject = subject - mail.contentType = Mail.CONTENTTYPE_HTML - mail.setTo(to) - mail.content = content - sendMail.send(mail, attachments = mailAttachments) + fun sendMail(from: String, to: List, subject: String, content: String, mailAttachments: List? = null) { + try { + if (content.isNotEmpty()) { + val mail = Mail() + mail.subject = subject + mail.contentType = Mail.CONTENTTYPE_HTML + mail.content = content + mail.from = from + to.forEach { mail.addTo(it) } + sendMail.send(mail, attachments = mailAttachments) + } + } catch (e: Exception) { + log.error(e.toString()) + } } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 1b569094f6..5adb05cab0 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -196,7 +196,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val processedLayout = LayoutUtils.processEditPage(layout, dto, this) processedLayout.actions.filterIsInstance().find { it.id == "create" - }?.confirmMessage = "poll.confirmation.creation" + }?.confirmMessage = translateMsg("poll.confirmation.creation") return processedLayout } @@ -210,10 +210,29 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } - override fun onAfterSaveOrUpdate(request: HttpServletRequest, poll: PollDO, postData: PostData) { - super.onAfterSaveOrUpdate(request, poll, postData) - // todo i18n hier einfügen vielleicht - pollMailService.sendMail(subject = "", content = "", to = "test.mail") + override fun onAfterSaveOrUpdate(request: HttpServletRequest, obj: PollDO, postData: PostData) { + val mailTo: ArrayList = ArrayList() + // add all attendees mails + postData.data.attendees?.map { it.email.toString() }?.let { mailTo.addAll(it) } + val owner = userService.getUser(obj.owner?.id) + val mailFrom = owner?.email.toString() + val mailSubject: String + val mailContent: String + + if (postData.data.isAlreadyCreated()) { + mailSubject = translateMsg("poll.mail.update.subject") + mailContent = translateMsg( + "poll.mail.update.content", obj.title, owner?.displayName + ) + } else { + mailSubject = translateMsg("poll.mail.created.subject") + mailContent = translateMsg( + "poll.mail.created.content", obj.title, owner?.displayName + ) + } + pollMailService.sendMail(mailFrom, mailTo, mailSubject, mailContent) + + super.onAfterSaveOrUpdate(request, obj, postData) } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt index e010a7ecbc..e0dfa4eb5c 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt @@ -32,7 +32,6 @@ class ExcelExport { fun getExcel(poll: Poll): ByteArray? { val responses = pollResponseDao.internalLoadAll().filter { it.poll?.id == poll.id } - val classPathResource = ClassPathResource("officeTemplates/PollResultTemplate" + ".xlsx") try { @@ -82,7 +81,6 @@ class ExcelExport { } excelRow.getCell(merge).setCellValue(question.question) excelSheet.autosize(merge) - // cuter -1 because the counter-- excelSheet.addMergeRegion(CellRangeAddress(0, 0, merge, counter)) merge = counter From 764c13351419662a6c0de23118666f6bd4336f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 16 May 2023 13:08:08 +0200 Subject: [PATCH 089/160] Added confirm messsage when no attendees were added --- .../main/resources/I18nResources.properties | 3 +++ .../resources/I18nResources_de.properties | 14 +++-------- .../projectforge/rest/poll/PollCronJobs.kt | 7 +++--- .../projectforge/rest/poll/PollPageRest.kt | 24 ++++++++++++++----- .../projectforge/rest/poll/PollResponse.kt | 5 ++-- .../rest/poll/ResponsePageRest.kt | 14 +++++------ .../rest/poll/excel/ExcelExport.kt | 4 +--- .../projectforge/rest/poll/types/Question.kt | 4 +--- .../rest/poll/types/QuestionDO.kt | 4 ---- 9 files changed, 38 insertions(+), 41 deletions(-) delete mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/QuestionDO.kt diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index 67e0403b0d..b326f3a18e 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -1980,8 +1980,11 @@ poll.attendees=Attendees poll.button.addQuestion=Add own question poll.button.micromataTemplate=Use Micromata Template poll.confirmation.creation=Do you really want to create the poll? You won't be able to edit the questions afterwards. +poll.confirmation.creationNoAttendees=Do you really want to create the poll? You won't be able to edit the questions afterwards.\ + You also have not added any attendees yet. Be sure to add attendees for your poll. poll.confirmation.deleteAnswer=Do you really want to delete this answer? poll.confirmation.deleteQuestion=Do you really want to delete this question? +poll.popup.closed=Umfrage wurde bereits beendet poll.date=Date poll.deadline=Deadline poll.description=Description diff --git a/projectforge-business/src/main/resources/I18nResources_de.properties b/projectforge-business/src/main/resources/I18nResources_de.properties index 338d7432f1..cc0e1761b0 100644 --- a/projectforge-business/src/main/resources/I18nResources_de.properties +++ b/projectforge-business/src/main/resources/I18nResources_de.properties @@ -5,7 +5,6 @@ # This main function sorts all entries in default properties and ensures the same output in this lang properties. # # Any comment or blank line of this file will be ignored and replaced by such lines from default properties. - # Missed translations from default 'I18nResources.properties' (might be OK): # # exception.notYetSupported=Not yet supported. @@ -88,8 +87,6 @@ # system.pluginAdmin.button.deactivate=Deactivate # system.pluginAdmin.title=Plugins # system.statistics.databasePool=Data base pool - - # Wicket: datatable.no-records-found=Keine Einträge gefunden. NavigatorLabel=Zeile ${from} bis ${to} von ${of}. @@ -105,19 +102,15 @@ validation.error.range.integerOutOfRange=Wert außerhalb des Bereichs {0} - {1}. validation.error.range.integerToHigh=Wert darf nicht größer als {0} sein. validation.error.range.integerToLow=Wert darf nicht kleiner als {0} sein. validation.required.valueNotPresent=Wert ''{0}'' nicht gegeben. - # React UI select.placeholder=Auswählen... table.showing=Anzeige - # own validations: bicvalidator.wronglength=Das Feld ''${label}'' muss 8 oder 11 Zeichen lang sein. ibanvalidator.wronglength.de=Das Feld ''${label}'' beginnt mit ''DE''. Eine deutsche IBAN muss jedoch aus 22 Zeichen bestehen. - # Currency format currencyConverter.percentage.help=Es können sowohl Beträge als auch Prozentzahlen (z. B. 10%) eingegeben werden. currencyFormat={0,number,,##0.00} - # Not internationalized properties: ### not translated: exception.notYetSupported=Not yet supported. ### not translated: menu.adminGuide=Administration guide @@ -127,7 +120,6 @@ currencyFormat={0,number,,##0.00} ### not translated: menu.sqlConsole=SQL console ### not translated: menu.userGuide=Handbuch ### not translated: message.notYetImplemented=Not yet implemented. - # Buttons: add=Hinzufügen assign=Zuweisen @@ -190,7 +182,6 @@ updateAndNext=Ändern und nächster upload=Hochladen uptodate=aktuell wizard=Assistent - # Common akquise=Akquise changes=Änderungen @@ -374,7 +365,6 @@ value=Wert values=Werte weekOfYear=Kalenderwoche yes=Ja - access=Zugriffsrecht access.accessTable=Zugriffstabelle access.exception.demoUserHasNoAccess=Der oder die Demobenutzer:in ist für diese Aktion gesperrt. @@ -2072,8 +2062,11 @@ poll.attendees=Teilnehmer poll.button.addQuestion=Eigene Frage hinzufügen poll.button.micromataTemplate=Micromata Template verwenden poll.confirmation.creation=Möchtest du die Umfrage wirklich erstellen? Du kannst die Fragen danach nicht mehr bearbeiten. +poll.confirmation.creationNoAttendees=Möchten Sie die Umfrage wirklich erstellen? Sie können die Fragen anschließend nicht mehr bearbeiten.\ +Sie haben auch noch keine Teilnehmer hinzugefügt. Stellen Sie sicher, dass Sie Teilnehmer zu Ihrer Umfrage hinzufügen. poll.confirmation.deleteAnswer=Möchtest du diese Antwort wirklich löschen? poll.confirmation.deleteQuestion=Möchtest du diese Frage wirklich löschen? +poll.error.closed=Umfrage wurde bereits beendet poll.date=Datum poll.deadline=Antwortfrist poll.description=Beschreibung @@ -2724,7 +2717,6 @@ webauthn.registration.button.authenticate=WebAuthn webauthn.registration.button.authenticate.info=Du kannst hier registrierte WebAuthn-Token benutzen (z. B. Yubikey). webauthn.registration.button.register=Registrieren webauthn.title=WebAuthn (Fido2 etc.) - # (timeable) attributes attr.deletemodal.heading=Soll dieser Eintrag wirklich gelöscht werden? attr.deletemodal.question=Ja: Der Eintrag wird gelöscht und alle Änderungen auf dieser Seite werden gespeichert.
Abbrechen: Der Eintrag wird nicht gelöscht und Sie bleiben auf dieser Seite. diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt index f02fb6d84a..664d6dd8c9 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt @@ -6,7 +6,6 @@ import org.projectforge.framework.i18n.translateMsg import org.projectforge.mail.MailAttachment import org.projectforge.rest.poll.excel.ExcelExport import org.springframework.beans.factory.annotation.Autowired -import java.util.* import org.springframework.scheduling.annotation.Scheduled import org.springframework.web.bind.annotation.RestController import java.time.LocalDate @@ -14,7 +13,7 @@ import java.time.LocalDateTime import java.time.ZoneId import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit -import kotlin.collections.ArrayList +import java.util.* @RestController class PollCronJobs { @@ -66,7 +65,7 @@ class PollCronJobs { } } // add all attendees mails - var mailTo: ArrayList = poll.attendees?.map { it.email } as ArrayList + val mailTo: ArrayList = poll.attendees?.map { it.email } as ArrayList val mailFrom = pollDO.owner?.email.toString() val mailSubject = translateMsg("poll.mail.ended.subject") val mailContent = translateMsg("poll.mail.ended.content", pollDO.title, pollDO.owner?.displayName) @@ -83,7 +82,7 @@ class PollCronJobs { val daysDifference = ChronoUnit.DAYS.between(LocalDate.now(), pollDO.deadline) if (daysDifference == 1L || daysDifference == 7L) { // add all attendees mails - var mailTo: ArrayList = poll.attendees?.map { it.email } as ArrayList + val mailTo: ArrayList = poll.attendees?.map { it.email } as ArrayList val mailFrom = pollDO.owner?.email.toString() val mailSubject = translateMsg("poll.mail.endingSoon.subject", daysDifference) val mailContent = translateMsg( diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 5adb05cab0..3c371f30b8 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -55,6 +55,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return result } + override fun transformForDB(dto: Poll): PollDO { val pollDO = PollDO() dto.copyTo(pollDO) @@ -65,10 +66,10 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } - //override fun transformForDB editMode not used - override fun transformFromDB(pollDO: PollDO, editMode: Boolean): Poll { + // override fun transformForDB editMode not used + override fun transformFromDB(obj: PollDO, editMode: Boolean): Poll { val poll = Poll() - poll.copyFrom(pollDO) + poll.copyFrom(obj) User.restoreDisplayNames(poll.fullAccessUsers, userService) Group.restoreDisplayNames(poll.fullAccessGroups, groupService) User.restoreDisplayNames(poll.attendees, userService) @@ -76,6 +77,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return poll } + override fun createListLayout( request: HttpServletRequest, layout: UILayout, magicFilter: MagicFilter, userAccess: UILayout.UserAccess ) { @@ -89,6 +91,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. .add(lc, "title", "description", "location", "owner", "deadline", "date", "state") } + override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { val layout = super.createEditLayout(dto, userAccess) @@ -131,7 +134,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) ) - fieldset .add(lc, "title", "description", "location") .add(lc, "owner") @@ -193,10 +195,16 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. layout.watchFields.addAll(listOf("groupAttendees")) + + val confirmMessage = if (dto.attendees.isNullOrEmpty()) { + translateMsg("poll.confirmation.creation") + } else { + translateMsg("poll.confirmation.creationNoAttendees") + } val processedLayout = LayoutUtils.processEditPage(layout, dto, this) processedLayout.actions.filterIsInstance().find { it.id == "create" - }?.confirmMessage = translateMsg("poll.confirmation.creation") + }?.confirmMessage = confirmMessage return processedLayout } @@ -264,7 +272,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val userAccess = UILayout.UserAccess(insert = true, update = true) val dto = postData.data - val type = BaseType.valueOf(dto.questionType ?: "poll.question.textQuestion") + val type = dto.questionType?.let { BaseType.valueOf(it) } ?: BaseType.TextQuestion val question = Question(uid = UUID.randomUUID().toString(), type = type) if (type == BaseType.SingleResponseQuestion) { question.answers = mutableListOf("yes", "no") @@ -327,6 +335,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) } + private fun addQuestionFieldset(layout: UILayout, dto: Poll) { dto.inputFields?.forEachIndexed { index, field -> val fieldset = UIFieldset(UILength(12), title = field.type.toString()) @@ -356,6 +365,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } } + private fun generateSingleAndMultiResponseAnswer( objGiven: Boolean, inputFieldIndex: Int, @@ -445,6 +455,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) } + @PostMapping("/export/{id}") fun export(@PathVariable("id") id: String): ResponseEntity? { val poll = Poll() @@ -462,6 +473,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return RestUtils.downloadFile(filename, bytes) } + // once created, questions should be ReadOnly private fun getUiElement(obj: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement { return if (obj) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt index 525d0d97a9..b3cd5863b4 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt @@ -6,7 +6,7 @@ import org.projectforge.business.poll.PollResponseDO import org.projectforge.framework.persistence.user.entities.PFUserDO import org.projectforge.rest.dto.BaseDTO -class PollResponse: BaseDTO() { +class PollResponse : BaseDTO() { var poll: PollDO? = null var owner: PFUserDO? = null var responses: MutableList? = mutableListOf() @@ -32,8 +32,7 @@ class QuestionAnswer { var answers: MutableList? = mutableListOf() - - fun toObject(string:String): QuestionAnswer { + fun toObject(string: String): QuestionAnswer { return ObjectMapper().readValue(string, QuestionAnswer::class.java) } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 4d048cab19..6e405162d7 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -5,8 +5,8 @@ import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao import org.projectforge.business.poll.PollResponseDO import org.projectforge.business.poll.PollResponseDao -import org.projectforge.framework.access.AccessCheckerImpl.I18N_KEY_VIOLATION_USER_NOT_MEMBER_OF import org.projectforge.framework.access.AccessException +import org.projectforge.framework.i18n.translateMsg import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.utils.NumberHelper import org.projectforge.rest.config.Rest @@ -20,7 +20,7 @@ import org.projectforge.ui.* import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* -import java.util.UUID +import java.util.* import javax.servlet.http.HttpServletRequest @RestController @@ -40,16 +40,16 @@ class ResponsePageRest : AbstractDynamicPageRest() { val pollDto = transformPollFromDB(pollData) if (pollDto.state == PollDO.State.FINISHED) { - throw AccessException(I18N_KEY_VIOLATION_USER_NOT_MEMBER_OF, "Umfrage wurde bereits beendet") + throw AccessException("access.exception.noAccess", "poll.error.closed") } val layout = UILayout("poll.response.title") val fieldSet = UIFieldset(12, title = pollDto.title) fieldSet - .add(UIReadOnlyField(value = pollDto.description, label = "Description")) - .add(UIReadOnlyField(value = pollDto.location, label = "Location")) - .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = "Owner")) - .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = "Deadline")) + .add(UIReadOnlyField(value = pollDto.description, label = translateMsg("poll.description"))) + .add(UIReadOnlyField(value = pollDto.location, label = translateMsg("poll.location"))) + .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = translateMsg("poll.owner"))) + .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = translateMsg("poll.deadline"))) layout.add(fieldSet) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt index e0dfa4eb5c..17c1c8ad0d 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt @@ -69,9 +69,7 @@ class ExcelExport { var merge = 1 poll.inputFields?.forEach { question -> - if (question.type == BaseType.MultiResponseQuestion || - question.type == BaseType.SingleResponseQuestion - ) { + if (question.type == BaseType.MultiResponseQuestion || question.type == BaseType.SingleResponseQuestion) { var counter = merge question.answers?.forEach { answer -> excelRow1.getCell(counter).setCellValue(answer) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt index a30bf4c442..019c76accb 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt @@ -22,7 +22,5 @@ class Question( } enum class BaseType { - TextQuestion, - SingleResponseQuestion, - MultiResponseQuestion, + TextQuestion, SingleResponseQuestion, MultiResponseQuestion, } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/QuestionDO.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/QuestionDO.kt deleted file mode 100644 index c9d74e7b64..0000000000 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/QuestionDO.kt +++ /dev/null @@ -1,4 +0,0 @@ -package org.projectforge.rest.poll.types - -class QuestionDO { -} \ No newline at end of file From e7f8ba163fa45cbd9f0d97d8346324f9b14c9caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Wed, 17 May 2023 09:30:07 +0200 Subject: [PATCH 090/160] Changed the flow between response and edit page --- .../kotlin/org/projectforge/menu/MenuItem.kt | 2 - .../main/resources/I18nResources.properties | 45 +++++---- .../resources/I18nResources_de.properties | 45 +++++---- .../projectforge/rest/poll/PollCronJobs.kt | 4 +- .../projectforge/rest/poll/PollMailService.kt | 6 +- .../projectforge/rest/poll/PollPageRest.kt | 61 +++--------- .../rest/poll/ResponsePageRest.kt | 95 +++++++++++++------ .../rest/poll/excel/ExcelExport.kt | 2 +- .../rest/poll/{ => types}/PollResponse.kt | 2 +- 9 files changed, 135 insertions(+), 127 deletions(-) rename projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/{ => types}/PollResponse.kt (96%) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/menu/MenuItem.kt b/projectforge-business/src/main/kotlin/org/projectforge/menu/MenuItem.kt index 42a054a1f3..defd6a2ab8 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/menu/MenuItem.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/menu/MenuItem.kt @@ -47,8 +47,6 @@ class MenuItem(var id: String? = null, if (menuItemDef == null) return id = menuItemDef.id - title = translate(menuItemDef.i18nKey) - i18nKey = menuItemDef.i18nKey key = menuItemDef.id url = menuItemDef.url if (menuItemDef.badgeCounter != null) { diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index b326f3a18e..dcaeb9ceb0 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -1975,37 +1975,40 @@ plugins.teamcal.title.edit=Edit team calendar plugins.teamcal.title.heading=Calendar plugins.teamcal.title.list=List of calendars # poll plugin -poll.answer=Answer +poll=Poll +poll.title=Title +poll.title.list=Polls +poll.title.add=Create new Poll +poll.title.edit=Edit Poll +poll.owner=Owner +poll.description=Description poll.attendees=Attendees +poll.groupAttendees=Attendee Groups +poll.fullAccessUsers=Full Access User +poll.fullAccessGroups=Full Access Groups +poll.date=Date +poll.deadline=Deadline +poll.location=Location +poll.state=State +poll.infopage=Info Page +poll.guide=Poll Guide +poll.question=Question +poll.question.textQuestion=Text Question +poll.questionType=Question Type +poll.answer=Answer poll.button.addQuestion=Add own question poll.button.micromataTemplate=Use Micromata Template poll.confirmation.creation=Do you really want to create the poll? You won't be able to edit the questions afterwards. poll.confirmation.creationNoAttendees=Do you really want to create the poll? You won't be able to edit the questions afterwards.\ - You also have not added any attendees yet. Be sure to add attendees for your poll. +You also have not added any attendees yet. Be sure to add attendees for your poll. poll.confirmation.deleteAnswer=Do you really want to delete this answer? poll.confirmation.deleteQuestion=Do you really want to delete this question? poll.popup.closed=Umfrage wurde bereits beendet -poll.date=Date -poll.deadline=Deadline -poll.description=Description poll.error.oneQuestionRequired=At least one question is required. -poll.fullAccessGroups=Full Access Groups -poll.fullAccessUsers=Full Access User -poll.groupAttendees=Attendee Groups -poll.guide=Poll Guide -poll.location=Location -poll.infopage=Info Page -poll.owner=Owner -poll.question=Question -poll.question.textQuestion=Text Question -poll.questionType=Question Type -poll.response.page=Poll Response Page -poll.state=State -poll.title=Title -poll.title.list=Polls -poll.title.add=Create new Poll poll.export.response.poll=Export results -poll=Poll +poll.respond=Send responses +poll.response.page=Poll Response Page +poll.response.title=Poll Response Page poll.mail.endingSoon.subject=Poll ending in {0} days poll.mail.endingSoon.content=

Dear Attendees,

\

This is a friendly reminder that the poll "{0}" created by {1} is ending soon, on {2}. Please make sure to submit your responses before the deadline.

\ diff --git a/projectforge-business/src/main/resources/I18nResources_de.properties b/projectforge-business/src/main/resources/I18nResources_de.properties index cc0e1761b0..1c7428c0c2 100644 --- a/projectforge-business/src/main/resources/I18nResources_de.properties +++ b/projectforge-business/src/main/resources/I18nResources_de.properties @@ -2057,37 +2057,40 @@ plugins.teamcal.title.edit=Team-Kalender bearbeiten plugins.teamcal.title.heading=Kalender plugins.teamcal.title.list=Kalenderliste # poll plugin -poll.answer=Antwort +poll=Umfrage +poll.title=Titel +poll.title.list=Umfragen +poll.title.add=Neue Umfrage erstellen +poll.title.edit=Umfrage bearbeiten +poll.owner=Eigentümer +poll.description=Beschreibung poll.attendees=Teilnehmer +poll.groupAttendees=Teilnehmergruppen +poll.fullAccessUsers=Benutzer mit Vollzugriff +poll.fullAccessGroups=Gruppen mit Vollzugriff +poll.date=Datum +poll.deadline=Antwortfrist +poll.location=Ort +poll.state=Status +poll.infopage=Infoseite +poll.guide=Anleitung +poll.question=Frage +poll.question.textQuestion=Textfrage +poll.questionType=Fragen Typ +poll.answer=Antwort poll.button.addQuestion=Eigene Frage hinzufügen poll.button.micromataTemplate=Micromata Template verwenden poll.confirmation.creation=Möchtest du die Umfrage wirklich erstellen? Du kannst die Fragen danach nicht mehr bearbeiten. poll.confirmation.creationNoAttendees=Möchten Sie die Umfrage wirklich erstellen? Sie können die Fragen anschließend nicht mehr bearbeiten.\ Sie haben auch noch keine Teilnehmer hinzugefügt. Stellen Sie sicher, dass Sie Teilnehmer zu Ihrer Umfrage hinzufügen. -poll.confirmation.deleteAnswer=Möchtest du diese Antwort wirklich löschen? poll.confirmation.deleteQuestion=Möchtest du diese Frage wirklich löschen? +poll.confirmation.deleteAnswer=Möchtest du diese Antwort wirklich löschen? poll.error.closed=Umfrage wurde bereits beendet -poll.date=Datum -poll.deadline=Antwortfrist -poll.description=Beschreibung poll.error.oneQuestionRequired=Mindestens eine Frage ist erforderlich. -poll.fullAccessGroups=Gruppen mit Vollzugriff -poll.fullAccessUsers=Benutzer mit Vollzugriff -poll.groupAttendees=Teilnehmergruppen -poll.infopage=Infoseite -poll.guide=Anleitung -poll.location=Ort -poll.owner=Eigentümer -poll.question=Frage -poll.question.textQuestion=Textfrage -poll.questionType=Fragen Typ -poll.response.page=Seite zur Umfrageantwort -poll.state=Status -poll.title=Titel -poll.title.list=Umfragen -poll.title.add=Neue Umfrage erstellen poll.export.response.poll=Ergebnisse exportieren -poll=Umfrage +poll.respond=Antworten abschicken +poll.response.page=Seite zur Umfrageantwort +poll.response.title=Seite zur Umfrageantwort poll.mail.endingSoon.subject=Umfrage endet in {0} Tagen poll.mail.endingSoon.content=

Liebe Teilnehmerinnen und Teilnehmer,

\

wir möchten Sie daran erinnern, dass die Umfrage "{0}", erstellt von {1}, bald endet, nämlich am {2}. Bitte achten Sie darauf, Ihre Antworten vor dem Ablaufdatum einzureichen.

\ diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt index 664d6dd8c9..6e48011296 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt @@ -65,7 +65,7 @@ class PollCronJobs { } } // add all attendees mails - val mailTo: ArrayList = poll.attendees?.map { it.email } as ArrayList + val mailTo: ArrayList = ArrayList(poll.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) val mailFrom = pollDO.owner?.email.toString() val mailSubject = translateMsg("poll.mail.ended.subject") val mailContent = translateMsg("poll.mail.ended.content", pollDO.title, pollDO.owner?.displayName) @@ -82,7 +82,7 @@ class PollCronJobs { val daysDifference = ChronoUnit.DAYS.between(LocalDate.now(), pollDO.deadline) if (daysDifference == 1L || daysDifference == 7L) { // add all attendees mails - val mailTo: ArrayList = poll.attendees?.map { it.email } as ArrayList + val mailTo: ArrayList = ArrayList(poll.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) val mailFrom = pollDO.owner?.email.toString() val mailSubject = translateMsg("poll.mail.endingSoon.subject", daysDifference) val mailContent = translateMsg( diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt index 733f5d9cef..38bc3d6b17 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt @@ -3,10 +3,10 @@ package org.projectforge.rest.poll import org.projectforge.mail.Mail import org.projectforge.mail.MailAttachment import org.projectforge.mail.SendMail -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Service import org.slf4j.Logger import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Service @Service class PollMailService { @@ -18,7 +18,7 @@ class PollMailService { fun sendMail(from: String, to: List, subject: String, content: String, mailAttachments: List? = null) { try { - if (content.isNotEmpty()) { + if (content.isNotEmpty() && to.isNotEmpty()) { val mail = Mail() mail.subject = subject mail.contentType = Mail.CONTENTTYPE_HTML diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 3c371f30b8..b576440ae4 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -77,18 +77,21 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return poll } + /** + * @return the response page. + */ + override fun getStandardEditPage(): String { + return "${PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java)}:id" + } override fun createListLayout( request: HttpServletRequest, layout: UILayout, magicFilter: MagicFilter, userAccess: UILayout.UserAccess ) { - agGridSupport.prepareUIGrid4ListPage( - request, - layout, - magicFilter, - this, - userAccess = userAccess, + val pollLC = LayoutContext(lc) + layout.add( + UITable.createUIResultSetTable() + .add(pollLC, "title", "description", "location", "owner", "deadline", "date", "state") ) - .add(lc, "title", "description", "location", "owner", "deadline", "date", "state") } @@ -96,43 +99,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val layout = super.createEditLayout(dto, userAccess) val fieldset = UIFieldset(UILength(12)) - if (dto.state == PollDO.State.RUNNING && dto.isAlreadyCreated()) { - fieldset.add( - UIButton.createDefaultButton( - id = "response-poll-button", - responseAction = ResponseAction( - PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "${dto.id}", - targetType = TargetType.REDIRECT - ), - title = "poll.response.page" - ) - ).add( - UIButton.createExportButton( - id = "export-poll-response-button", - responseAction = ResponseAction("${Rest.URL}/poll/export/${dto.id}", targetType = TargetType.POST), - title = "poll.export.response.poll" - ) - ) - } - fieldset.add( - UIRow().add( - UICol( - UILength(10) - ) - ).add( - UICol( - UILength(1) - ).add( - UIButton.createLinkButton( - id = "poll-guide", title = "poll.guide", responseAction = ResponseAction( - PagesResolver.getDynamicPageUrl( - PollInfoPageRest::class.java, absolute = true - ), targetType = TargetType.MODAL - ) - ) - ) - ) - ) fieldset .add(lc, "title", "description", "location") @@ -197,9 +163,9 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val confirmMessage = if (dto.attendees.isNullOrEmpty()) { - translateMsg("poll.confirmation.creation") - } else { translateMsg("poll.confirmation.creationNoAttendees") + } else { + translateMsg("poll.confirmation.creation") } val processedLayout = LayoutUtils.processEditPage(layout, dto, this) processedLayout.actions.filterIsInstance().find { @@ -219,9 +185,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun onAfterSaveOrUpdate(request: HttpServletRequest, obj: PollDO, postData: PostData) { - val mailTo: ArrayList = ArrayList() // add all attendees mails - postData.data.attendees?.map { it.email.toString() }?.let { mailTo.addAll(it) } + val mailTo: ArrayList = ArrayList(postData.data.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) val owner = userService.getUser(obj.owner?.id) val mailFrom = owner?.email.toString() val mailSubject: String diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 6e405162d7..c5e443e3e7 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -9,6 +9,8 @@ import org.projectforge.framework.access.AccessException import org.projectforge.framework.i18n.translateMsg import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.utils.NumberHelper +import org.projectforge.menu.MenuItem +import org.projectforge.menu.MenuItemTargetType import org.projectforge.rest.config.Rest import org.projectforge.rest.core.AbstractDynamicPageRest import org.projectforge.rest.core.PagesResolver @@ -34,7 +36,9 @@ class ResponsePageRest : AbstractDynamicPageRest() { private lateinit var pollResponseDao: PollResponseDao @GetMapping("dynamic") - fun getForm(request: HttpServletRequest, @RequestParam("id") pollStringId: String?): FormLayoutData { + fun getForm( + request: HttpServletRequest, @RequestParam("id") pollStringId: String? + ): FormLayoutData { val id = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") val pollData = pollDao.internalGetById(id) ?: PollDO() val pollDto = transformPollFromDB(pollData) @@ -44,21 +48,67 @@ class ResponsePageRest : AbstractDynamicPageRest() { } val layout = UILayout("poll.response.title") - val fieldSet = UIFieldset(12, title = pollDto.title) - fieldSet - .add(UIReadOnlyField(value = pollDto.description, label = translateMsg("poll.description"))) + val fieldset = UIFieldset(12, title = pollDto.title) + + if (pollDto.state == PollDO.State.RUNNING && pollDto.isAlreadyCreated()) { + fieldset.add( + UIButton.createDefaultButton( + id = "response-poll-button", + responseAction = ResponseAction( + PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "${pollDto.id}", + targetType = TargetType.REDIRECT + ), + title = "poll.response.page" + ) + ).add( + UIButton.createExportButton( + id = "export-poll-response-button", + responseAction = ResponseAction("${Rest.URL}/poll/export/${pollDto.id}", targetType = TargetType.POST), + title = "poll.export.response.poll" + ) + ) + } + fieldset.add( + UIRow().add( + UICol( + UILength(10) + ) + ).add( + UICol( + UILength(1) + ).add( + UIButton.createLinkButton( + id = "poll-guide", title = "poll.guide", responseAction = ResponseAction( + PagesResolver.getDynamicPageUrl( + PollInfoPageRest::class.java, absolute = true + ), targetType = TargetType.MODAL + ) + ) + ) + ) + ) + + fieldset.add(UIReadOnlyField(value = pollDto.description, label = translateMsg("poll.description"))) .add(UIReadOnlyField(value = pollDto.location, label = translateMsg("poll.location"))) .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = translateMsg("poll.owner"))) .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = translateMsg("poll.deadline"))) - layout.add(fieldSet) + layout.add(fieldset) + + layout.add( + MenuItem( + "EDIT", + i18nKey = "poll.title.edit", + url = PagesResolver.getEditPageUrl(PollPageRest::class.java, pollDto.id), + type = MenuItemTargetType.REDIRECT + ) + ) val pollResponse = PollResponse() pollResponse.poll = pollData pollResponseDao.internalLoadAll().firstOrNull { response -> - response.owner == ThreadLocalUserContext.user - && response.poll?.id == pollData.id + response.owner == ThreadLocalUserContext.user && response.poll?.id == pollData.id }?.let { pollResponse.copyFrom(it) } @@ -71,8 +121,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { pollResponse.responses?.firstOrNull { it.questionUid == field.uid }.let { - if (it == null) - pollResponse.responses?.add(questionAnswer) + if (it == null) pollResponse.responses?.add(questionAnswer) } val col = UICol() @@ -83,16 +132,12 @@ class ResponsePageRest : AbstractDynamicPageRest() { if (field.type == BaseType.SingleResponseQuestion) { col.add( UIRadioButton( - "responses[$index].answers[0]", - value = field.answers!![0], - label = field.answers?.get(0) ?: "" + "responses[$index].answers[0]", value = field.answers!![0], label = field.answers?.get(0) ?: "" ) ) col.add( UIRadioButton( - "responses[$index].answers[0]", - value = field.answers!![1], - label = field.answers?.get(1) ?: "" + "responses[$index].answers[0]", value = field.answers!![1], label = field.answers?.get(1) ?: "" ) ) } @@ -110,24 +155,21 @@ class ResponsePageRest : AbstractDynamicPageRest() { layout.add( UIButton.createDefaultButton( - id = "doResponse", - title = "response", - responseAction = ResponseAction( + id = "doResponse", title = translateMsg("poll.respond"), responseAction = ResponseAction( RestResolver.getRestUrl( - this::class.java, - "doResponse" + this::class.java, "doResponse" ), targetType = TargetType.POST ) ) ) + LayoutUtils.process(layout) return FormLayoutData(pollResponse, layout, createServerData(request)) } @PostMapping("doResponse") fun doResponse( - request: HttpServletRequest, - @RequestBody postData: PostData + request: HttpServletRequest, @RequestBody postData: PostData ): ResponseEntity? { val pollResponseDO = PollResponseDO() @@ -136,15 +178,13 @@ class ResponsePageRest : AbstractDynamicPageRest() { pollResponseDO.owner = ThreadLocalUserContext.user pollResponseDao.internalLoadAll().firstOrNull { pollResponse -> - pollResponse.owner == ThreadLocalUserContext.user - && pollResponse.poll?.id == postData.data.poll?.id + pollResponse.owner == ThreadLocalUserContext.user && pollResponse.poll?.id == postData.data.poll?.id }?.let { it.responses = pollResponseDO.responses pollResponseDao.update(it) return ResponseEntity.ok( ResponseAction( - targetType = TargetType.REDIRECT, - url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + targetType = TargetType.REDIRECT, url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) ) ) } @@ -154,8 +194,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { return ResponseEntity.ok( ResponseAction( - targetType = TargetType.REDIRECT, - url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + targetType = TargetType.REDIRECT, url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) ) ) } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt index 17c1c8ad0d..036648f6ab 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt @@ -9,8 +9,8 @@ import org.apache.poi.ss.util.CellRangeAddress import org.projectforge.business.poll.PollResponseDao import org.projectforge.rest.dto.User import org.projectforge.rest.poll.Poll -import org.projectforge.rest.poll.PollResponse import org.projectforge.rest.poll.types.BaseType +import org.projectforge.rest.poll.types.PollResponse import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt similarity index 96% rename from projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt rename to projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt index b3cd5863b4..f03406cb66 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponse.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt @@ -1,4 +1,4 @@ -package org.projectforge.rest.poll +package org.projectforge.rest.poll.types import com.fasterxml.jackson.databind.ObjectMapper import org.projectforge.business.poll.PollDO From acd77ca5125a1d6458b13ba79f95b6e6bd17ab4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Wed, 17 May 2023 10:26:46 +0200 Subject: [PATCH 091/160] i18n fix --- .../src/main/resources/I18nResources.properties | 4 ++-- .../src/main/resources/I18nResources_de.properties | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index dcaeb9ceb0..11176b5428 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -2003,7 +2003,7 @@ poll.confirmation.creationNoAttendees=Do you really want to create the poll? You You also have not added any attendees yet. Be sure to add attendees for your poll. poll.confirmation.deleteAnswer=Do you really want to delete this answer? poll.confirmation.deleteQuestion=Do you really want to delete this question? -poll.popup.closed=Umfrage wurde bereits beendet +poll.popup.closed=The poll was already closed. poll.error.oneQuestionRequired=At least one question is required. poll.export.response.poll=Export results poll.respond=Send responses @@ -2025,7 +2025,7 @@ poll.mail.ended.content=

Dear Attendees,

\

Best regards,

\

{1}

poll.mail.update.subject=Poll was edited -poll.mail.update.content=

Liebe Teilnehmerinnen und Teilnehmer,

\ +poll.mail.update.content=

Dear Attendees,

\

We wanted to let you know that the poll "{0}" was edited recently.

\

If you already submitted your answers, you should check, if there were any major changes made.

\

Thank you again for your participation.

\ diff --git a/projectforge-business/src/main/resources/I18nResources_de.properties b/projectforge-business/src/main/resources/I18nResources_de.properties index 1c7428c0c2..338f95a5db 100644 --- a/projectforge-business/src/main/resources/I18nResources_de.properties +++ b/projectforge-business/src/main/resources/I18nResources_de.properties @@ -2088,6 +2088,7 @@ poll.confirmation.deleteAnswer=M poll.error.closed=Umfrage wurde bereits beendet poll.error.oneQuestionRequired=Mindestens eine Frage ist erforderlich. poll.export.response.poll=Ergebnisse exportieren +poll.popup.closed=Umfrage wurde bereits beendet poll.respond=Antworten abschicken poll.response.page=Seite zur Umfrageantwort poll.response.title=Seite zur Umfrageantwort From 2f441294f656107c90a3b51299306510746e7286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Wed, 17 May 2023 10:48:45 +0200 Subject: [PATCH 092/160] Merge bugs gefixt --- .../kotlin/org/projectforge/rest/poll/PollPageRest.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index bb224bcf82..8f5fdda23d 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -125,12 +125,11 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. .add( UIButton.createDefaultButton( id = "add-question-button", - title = "Eigene Frage hinzufügen", + title = "poll.button.addQuestion", responseAction = ResponseAction( "${Rest.URL}/poll/add", targetType = TargetType.POST ), - title = "poll.button.addQuestion" ) ) ) @@ -442,9 +441,9 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. /** * Once created, questions should be ReadOnly */ - private fun getUiElement(obj: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement{ - if (obj) - return UIReadOnlyField(id, label = label, dataType = dataType) + private fun getUiElement(obj: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement { + return if (obj) + UIReadOnlyField(id, label = label, dataType = dataType) else UIInput(id, label = label, dataType = dataType) } From fe9b49d80d619b9e5226902a2706a6ecd71c96a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Wed, 17 May 2023 11:31:39 +0200 Subject: [PATCH 093/160] Fixed bug --- .../src/main/kotlin/org/projectforge/menu/MenuItem.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/menu/MenuItem.kt b/projectforge-business/src/main/kotlin/org/projectforge/menu/MenuItem.kt index defd6a2ab8..42a054a1f3 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/menu/MenuItem.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/menu/MenuItem.kt @@ -47,6 +47,8 @@ class MenuItem(var id: String? = null, if (menuItemDef == null) return id = menuItemDef.id + title = translate(menuItemDef.i18nKey) + i18nKey = menuItemDef.i18nKey key = menuItemDef.id url = menuItemDef.url if (menuItemDef.badgeCounter != null) { From e76a7c83d09a9fb23dbceede2639bcda88da5ac0 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 17 May 2023 13:02:48 +0200 Subject: [PATCH 094/160] you can change the state to finish --- .../main/resources/I18nResources.properties | 10 +- .../resources/I18nResources_de.properties | 2 + .../projectforge/rest/poll/PollPageRest.kt | 97 ++++++++++++++++--- 3 files changed, 86 insertions(+), 23 deletions(-) diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index 11176b5428..e9cfd90bf1 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -13,19 +13,15 @@ validation.error.range.integerOutOfRange=Value out of range {0}-{1}. validation.error.range.integerToHigh=Value must not be higher than {0}. validation.error.range.integerToLow=Value must not be lower than {0}. validation.required.valueNotPresent=Value ''{0}'' not present. - # React UI select.placeholder=Select... table.showing=Showing - # own validations: bicvalidator.wronglength=The field ''${label}'' must be 8 or 11 characters long. ibanvalidator.wronglength.de=The field ''${label}'' starts with ''DE'', but a german IBAN must have 22 characters. - # Currency format currencyConverter.percentage.help=You can enter amounts as well as percent values (e. g. 10%). currencyFormat={0,number,,##0.00} - # Not internationalized properties: exception.notYetSupported=Not yet supported. menu.adminGuide=Administration guide @@ -35,7 +31,6 @@ menu.projectDocumentation=Project docs menu.sqlConsole=SQL console menu.userGuide=Handbuch message.notYetImplemented=Not yet implemented. - # Buttons: add=Add assign=Assign @@ -98,7 +93,6 @@ updateAndNext=Update and next upload=Upload uptodate=up-to-date wizard=Wizard - # Common akquise=Acquisition changes=Changes @@ -282,7 +276,6 @@ value=Value values=Values weekOfYear=Week of year yes=Yes - access=Accessright access.accessTable=Access table access.exception.demoUserHasNoAccess=The demo user is blocked for this action. @@ -1998,6 +1991,8 @@ poll.questionType=Question Type poll.answer=Answer poll.button.addQuestion=Add own question poll.button.micromataTemplate=Use Micromata Template +poll.button.finish=Finish Poll +poll.confirmation.finish=Do you really want to Finish this Poll? poll.confirmation.creation=Do you really want to create the poll? You won't be able to edit the questions afterwards. poll.confirmation.creationNoAttendees=Do you really want to create the poll? You won't be able to edit the questions afterwards.\ You also have not added any attendees yet. Be sure to add attendees for your poll. @@ -2635,7 +2630,6 @@ webauthn.registration.button.authenticate=WebAuthn webauthn.registration.button.authenticate.info=You may use any of your registered WebAuthn tokens here (for example Yubikey). webauthn.registration.button.register=Register webauthn.title=WebAuthn (Fido2 etc.) - # (timeable) attributes attr.deletemodal.heading=Would you like to delete this entry? attr.deletemodal.question=Yes: This entry will be deleted and all changes on this page will be saved.
Cancel: This entry will not be deleted and you stay on this page. diff --git a/projectforge-business/src/main/resources/I18nResources_de.properties b/projectforge-business/src/main/resources/I18nResources_de.properties index 338f95a5db..87896648cc 100644 --- a/projectforge-business/src/main/resources/I18nResources_de.properties +++ b/projectforge-business/src/main/resources/I18nResources_de.properties @@ -2080,6 +2080,8 @@ poll.questionType=Fragen Typ poll.answer=Antwort poll.button.addQuestion=Eigene Frage hinzufügen poll.button.micromataTemplate=Micromata Template verwenden +poll.button.finish=Umfrage beenden +poll.confirmation.finish=Willst du die Umfrage wirklich beenden? poll.confirmation.creation=Möchtest du die Umfrage wirklich erstellen? Du kannst die Fragen danach nicht mehr bearbeiten. poll.confirmation.creationNoAttendees=Möchten Sie die Umfrage wirklich erstellen? Sie können die Fragen anschließend nicht mehr bearbeiten.\ Sie haben auch noch keine Teilnehmer hinzugefügt. Stellen Sie sicher, dass Sie Teilnehmer zu Ihrer Umfrage hinzufügen. diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 8f5fdda23d..83247f1af8 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -9,6 +9,7 @@ import org.projectforge.framework.access.AccessException import org.projectforge.framework.i18n.translateMsg import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext +import org.projectforge.model.rest.RestPaths import org.projectforge.rest.config.Rest import org.projectforge.rest.config.RestUtils import org.projectforge.rest.core.* @@ -24,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.core.io.Resource import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* +import java.time.LocalDate import java.time.LocalDateTime import java.util.* import javax.servlet.http.HttpServletRequest @@ -128,7 +130,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. title = "poll.button.addQuestion", responseAction = ResponseAction( "${Rest.URL}/poll/add", - targetType = TargetType.POST + targetType = TargetType.PUT ), ) ) @@ -166,14 +168,43 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } else { translateMsg("poll.confirmation.creation") } + val processedLayout = LayoutUtils.processEditPage(layout, dto, this) processedLayout.actions.filterIsInstance().find { it.id == "create" }?.confirmMessage = confirmMessage + + + if (getId(dto) != null) { + processedLayout.addAction( + UIButton.createDangerButton( + id = "poll-button-finish", + title = "poll.button.finish", + confirmMessage = translateMsg("poll.confirmation.finish"), + responseAction = ResponseAction( + "${Rest.URL}/poll/finish", + targetType = TargetType.PUT + ), + layout = layout, + ) + ) + } + return processedLayout } + @PutMapping("/finish") + fun changeStatToFinish( + request: HttpServletRequest, + @RequestBody postData: PostData + ): ResponseEntity { + postData.data.state = PollDO.State.FINISHED + postData.data.deadline = LocalDate.now() + + return super.saveOrUpdate(request, postData) + } + override fun onBeforeSaveOrUpdate(request: HttpServletRequest, obj: PollDO, postData: PostData) { if (obj.inputFields.isNullOrEmpty() || obj.inputFields.equals("[]")) { throw AccessException("poll.error.oneQuestionRequired") @@ -185,7 +216,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun onAfterSaveOrUpdate(request: HttpServletRequest, obj: PollDO, postData: PostData) { // add all attendees mails - val mailTo: ArrayList = ArrayList(postData.data.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) + val mailTo: ArrayList = + ArrayList(postData.data.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) val owner = userService.getUser(obj.owner?.id) val mailFrom = owner?.email.toString() val mailSubject: String @@ -245,7 +277,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.inputFields!!.add(question) dto.owner = userService.getUser(dto.owner?.id) return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto) + .addVariable("ui", createEditLayout(dto, userAccess)) ) } @@ -295,7 +328,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto) + .addVariable("ui", createEditLayout(dto, userAccess)) ) } @@ -306,11 +340,25 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. if (!dto.isAlreadyCreated()) { fieldset.add(generateDeleteButton(layout, field.uid)) } - fieldset.add(getUiElement(dto.isAlreadyCreated(), "inputFields[${index}].question", translateMsg("poll.question"))) + fieldset.add( + getUiElement( + dto.isAlreadyCreated(), + "inputFields[${index}].question", + translateMsg("poll.question") + ) + ) if (field.type == BaseType.SingleResponseQuestion || field.type == BaseType.MultiResponseQuestion) { field.answers?.forEachIndexed { answerIndex, _ -> - fieldset.add(generateSingleAndMultiResponseAnswer(dto.isAlreadyCreated(), index, field.uid, answerIndex, layout)) + fieldset.add( + generateSingleAndMultiResponseAnswer( + dto.isAlreadyCreated(), + index, + field.uid, + answerIndex, + layout + ) + ) } if (!dto.isAlreadyCreated()) { fieldset.add( @@ -355,7 +403,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. UIButton.createDangerButton( id = "X", responseAction = ResponseAction( - "${Rest.URL}/poll/deleteAnswer/${questionUid}/${answerIndex}", targetType = TargetType.POST + "${Rest.URL}/poll/deleteAnswer/${questionUid}/${answerIndex}", + targetType = TargetType.POST ) ).withConfirmMessage(layout, confirmMessage = "poll.confirmation.deleteAnswer") ) @@ -378,7 +427,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.inputFields?.find { it.uid.equals(questionUid) }?.answers?.removeAt(answerIndex) dto.owner = userService.getUser(dto.owner?.id) return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto) + .addVariable("ui", createEditLayout(dto, userAccess)) ) } @@ -415,7 +465,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.inputFields?.remove(matchingQuestion) return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto) + .addVariable("ui", createEditLayout(dto, userAccess)) ) } @@ -441,7 +492,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. /** * Once created, questions should be ReadOnly */ - private fun getUiElement(obj: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement { + private fun getUiElement( + obj: Boolean, + id: String, + label: String? = null, + dataType: UIDataType = UIDataType.STRING + ): UIElement { return if (obj) UIReadOnlyField(id, label = label, dataType = dataType) else @@ -454,14 +510,25 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. .add(lc, "title", "description", "location") .add(UISelect.createUserSelect(lc, "owner", false, "poll.owner")) .add(lc, "deadline", "date") - } - else { + } else { fieldset .add(UIReadOnlyField(value = pollDto.title, label = "titel", dataType = UIDataType.STRING)) .add(UIReadOnlyField(value = pollDto.description, label = "description", dataType = UIDataType.STRING)) .add(UIReadOnlyField(value = pollDto.location, label = "location", dataType = UIDataType.STRING)) - .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = "deadline", dataType = UIDataType.STRING)) - .add(UIReadOnlyField(value = (pollDto.date?.toString() ?: ""), label = "date", dataType = UIDataType.STRING)) + .add( + UIReadOnlyField( + value = pollDto.deadline.toString(), + label = "deadline", + dataType = UIDataType.STRING + ) + ) + .add( + UIReadOnlyField( + value = (pollDto.date?.toString() ?: ""), + label = "date", + dataType = UIDataType.STRING + ) + ) .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = "owner", dataType = UIDataType.STRING)) } } @@ -483,7 +550,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. UILayout.UserAccess(insert = true, update = true, delete = false, history = true) } else { // full access when viewing old poll - UILayout.UserAccess(insert = false, update = false, delete = true, history = false) + UILayout.UserAccess(insert = false, update = false, delete = true, history = false) } } } From 79a0a8c59d72914332798dc0122cabcdb2b0e2be Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 17 May 2023 13:28:07 +0200 Subject: [PATCH 095/160] enter triger only Crete Button --- .../projectforge/rest/poll/PollPageRest.kt | 79 ++++++++++++++----- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 8f5fdda23d..8ca63481ef 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -128,8 +128,9 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. title = "poll.button.addQuestion", responseAction = ResponseAction( "${Rest.URL}/poll/add", - targetType = TargetType.POST + targetType = TargetType.PUT ), + default = false ) ) ) @@ -146,9 +147,10 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. id = "micromata-template-button", responseAction = ResponseAction( "${Rest.URL}/poll/addPremadeQuestions", - targetType = TargetType.POST + targetType = TargetType.PUT ), - title = "poll.button.micromataTemplate" + title = "poll.button.micromataTemplate", + default = false ) ) ) @@ -185,7 +187,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun onAfterSaveOrUpdate(request: HttpServletRequest, obj: PollDO, postData: PostData) { // add all attendees mails - val mailTo: ArrayList = ArrayList(postData.data.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) + val mailTo: ArrayList = + ArrayList(postData.data.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) val owner = userService.getUser(obj.owner?.id) val mailFrom = owner?.email.toString() val mailSubject: String @@ -229,7 +232,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. // PostMapping add - @PostMapping("/add") + @PutMapping("/add") fun addQuestionField( @RequestBody postData: PostData ): ResponseEntity { @@ -245,7 +248,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.inputFields!!.add(question) dto.owner = userService.getUser(dto.owner?.id) return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto) + .addVariable("ui", createEditLayout(dto, userAccess)) ) } @@ -283,7 +287,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } - @PostMapping("/addPremadeQuestions") + @PutMapping("/addPremadeQuestions") private fun addPremadeQuestionsField( @RequestBody postData: PostData, ): ResponseEntity { @@ -295,7 +299,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto) + .addVariable("ui", createEditLayout(dto, userAccess)) ) } @@ -306,11 +311,25 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. if (!dto.isAlreadyCreated()) { fieldset.add(generateDeleteButton(layout, field.uid)) } - fieldset.add(getUiElement(dto.isAlreadyCreated(), "inputFields[${index}].question", translateMsg("poll.question"))) + fieldset.add( + getUiElement( + dto.isAlreadyCreated(), + "inputFields[${index}].question", + translateMsg("poll.question") + ) + ) if (field.type == BaseType.SingleResponseQuestion || field.type == BaseType.MultiResponseQuestion) { field.answers?.forEachIndexed { answerIndex, _ -> - fieldset.add(generateSingleAndMultiResponseAnswer(dto.isAlreadyCreated(), index, field.uid, answerIndex, layout)) + fieldset.add( + generateSingleAndMultiResponseAnswer( + dto.isAlreadyCreated(), + index, + field.uid, + answerIndex, + layout + ) + ) } if (!dto.isAlreadyCreated()) { fieldset.add( @@ -318,7 +337,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. UIButton.createAddButton( responseAction = ResponseAction( "${Rest.URL}/poll/addAnswer/${field.uid}", targetType = TargetType.POST - ) + ), + default = false ) ) ) @@ -355,7 +375,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. UIButton.createDangerButton( id = "X", responseAction = ResponseAction( - "${Rest.URL}/poll/deleteAnswer/${questionUid}/${answerIndex}", targetType = TargetType.POST + "${Rest.URL}/poll/deleteAnswer/${questionUid}/${answerIndex}", + targetType = TargetType.POST ) ).withConfirmMessage(layout, confirmMessage = "poll.confirmation.deleteAnswer") ) @@ -378,7 +399,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.inputFields?.find { it.uid.equals(questionUid) }?.answers?.removeAt(answerIndex) dto.owner = userService.getUser(dto.owner?.id) return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto) + .addVariable("ui", createEditLayout(dto, userAccess)) ) } @@ -415,7 +437,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.inputFields?.remove(matchingQuestion) return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto) + .addVariable("ui", createEditLayout(dto, userAccess)) ) } @@ -441,7 +464,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. /** * Once created, questions should be ReadOnly */ - private fun getUiElement(obj: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement { + private fun getUiElement( + obj: Boolean, + id: String, + label: String? = null, + dataType: UIDataType = UIDataType.STRING + ): UIElement { return if (obj) UIReadOnlyField(id, label = label, dataType = dataType) else @@ -454,14 +482,25 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. .add(lc, "title", "description", "location") .add(UISelect.createUserSelect(lc, "owner", false, "poll.owner")) .add(lc, "deadline", "date") - } - else { + } else { fieldset .add(UIReadOnlyField(value = pollDto.title, label = "titel", dataType = UIDataType.STRING)) .add(UIReadOnlyField(value = pollDto.description, label = "description", dataType = UIDataType.STRING)) .add(UIReadOnlyField(value = pollDto.location, label = "location", dataType = UIDataType.STRING)) - .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = "deadline", dataType = UIDataType.STRING)) - .add(UIReadOnlyField(value = (pollDto.date?.toString() ?: ""), label = "date", dataType = UIDataType.STRING)) + .add( + UIReadOnlyField( + value = pollDto.deadline.toString(), + label = "deadline", + dataType = UIDataType.STRING + ) + ) + .add( + UIReadOnlyField( + value = (pollDto.date?.toString() ?: ""), + label = "date", + dataType = UIDataType.STRING + ) + ) .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = "owner", dataType = UIDataType.STRING)) } } @@ -483,7 +522,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. UILayout.UserAccess(insert = true, update = true, delete = false, history = true) } else { // full access when viewing old poll - UILayout.UserAccess(insert = false, update = false, delete = true, history = false) + UILayout.UserAccess(insert = false, update = false, delete = true, history = false) } } } From 3dda91a618ab8f8f63f6b501cc95cd407fc348df Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Wed, 17 May 2023 13:30:35 +0200 Subject: [PATCH 096/160] Merge branch 'overview' of /Users/jonafleckenstein/Documents/Ausbildung/projectforge-fork/umfrage/projectforge with conflicts. --- .../business/poll/PollResponseDO.kt | 3 + .../org/projectforge/rest/poll/CronJobs.kt | 56 ++++++++++++------- .../projectforge/rest/poll/PollMailService.kt | 27 ++++----- .../rest/poll/ResponsePageRest.kt | 3 +- 4 files changed, 55 insertions(+), 34 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt index 0f5399c391..613409eb6a 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt @@ -1,5 +1,7 @@ package org.projectforge.business.poll +import org.hibernate.annotations.OnDelete +import org.hibernate.annotations.OnDeleteAction import org.hibernate.search.annotations.Indexed import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.persistence.api.AUserRightId @@ -16,6 +18,7 @@ import javax.persistence.* open class PollResponseDO : DefaultBaseDO() { @get:PropertyInfo(i18nKey = "poll.response.poll") @get:ManyToOne(fetch = FetchType.LAZY) + @get:OnDelete(action = OnDeleteAction.CASCADE) @get:JoinColumn(name = "poll_fk", nullable = false) open var poll: PollDO? = null diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt index 3067feb52d..b6ba370ea1 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt @@ -2,6 +2,7 @@ package org.projectforge.rest.poll import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao +import org.projectforge.business.user.UserDao import org.projectforge.mail.MailAttachment import org.projectforge.rest.poll.Exel.ExcelExport import org.springframework.beans.factory.annotation.Autowired @@ -20,23 +21,27 @@ import org.slf4j.LoggerFactory class CronJobs { private val log: Logger = LoggerFactory.getLogger(CronJobs::class.java) + @Autowired private lateinit var pollDao: PollDao + @Autowired private lateinit var pollMailService: PollMailService + @Autowired + private lateinit var userDao: UserDao + /** * Cron job for daily stuff */ // @Scheduled(cron = "0 0 1 * * *") // 1am everyday - @Scheduled(cron = "0 * * * * *") // 1am everyday + @Scheduled(cron = "0 * * * * *") // Every Minute fun dailyCronJobs() { cronDeletePolls() cronEndPolls() } - /** * Method to end polls after deadline */ @@ -62,8 +67,9 @@ class CronJobs { val attachment = object : MailAttachment { override fun getFilename(): String { - return it.title+ "_" + LocalDateTime.now().year +"_Result"+ ".xlsx" + return it.title + "_" + LocalDateTime.now().year + "_Result" + ".xlsx" } + override fun getContent(): ByteArray? { return exel } @@ -71,7 +77,7 @@ class CronJobs { list.add(attachment) header = "Umfrage ist abgelaufen" - mail =""" + mail = """ Die Umfrage ist zu ende. Hier die ergebnisse. """.trimMargin() @@ -83,22 +89,24 @@ class CronJobs { try { // erstell mir eine funktion, die alles deadlines mir gibt die in der zukunft liegen - val pollsInFuture = polls.filter { it.deadline?.isAfter(LocalDate.now()) ?: false} - pollsInFuture.forEach{ + val pollsInFuture = polls.filter { it.deadline?.isAfter(LocalDate.now()) ?: false } + pollsInFuture.forEach { val daysDifference = ChronoUnit.DAYS.between(LocalDate.now(), it.deadline) - if(daysDifference == 1L || daysDifference == 7L){ + if (daysDifference == 1L || daysDifference == 7L) { header = "Umfrage Endet in $daysDifference Tage" - mail =""" + mail = """ Sehr geehrter Teilnehmer,wir laden Sie herzlich dazu ein, an unserer Umfrage zum Thema ${it.title} teilzunehmen. Ihre Meinung ist uns sehr wichtig und wir würden uns freuen, wenn Sie uns dabei helfen könnten, - unsere Forschungsergebnisse zu verbessern. Für diese Umfrage ist ${it ///owmer + unsere Forschungsergebnisse zu verbessern. Für diese Umfrage ist ${ + it ///owner }zuständig. Bei Fragen oder Anmerkungen können Sie sich gerne an ihn wenden. Bitte beachten Sie, dass das Enddatum für die Teilnahme an dieser Umfrage der ${it.deadline.toString()} ist. Wir würden uns freuen, wenn Sie sich die Zeit nehmen könnten, um diese Umfrage auszufüllen. Vielen Dank im Voraus für Ihre Unterstützung. - Mit freundlichen Grüßen,${it ///owmer + Mit freundlichen Grüßen,${ + it ///owmer } """.trimMargin() } @@ -106,10 +114,9 @@ class CronJobs { if (mail.isNotEmpty()) { - pollMailService.sendMail(to="test", subject = header, content = mail, mailAttachments = list) + pollMailService.sendMail(to = "test", subject = header, content = mail, mailAttachments = list) } - } - catch (e:Exception) { + } catch (e: Exception) { log.error(e.toString()) } } @@ -118,15 +125,24 @@ class CronJobs { /** * Method to delete old polls */ - fun cronDeletePolls() { + private fun cronDeletePolls() { + println("CRON JOB NOW!") // check if poll end in future val polls = pollDao.internalLoadAll() - val pollsMoreThanOneYearPast = polls.filter { it.created?.before(Date.from(LocalDate.now().minusYears(1).atStartOfDay( - ZoneId.systemDefault() - ).toInstant())) ?: false } - pollsMoreThanOneYearPast.forEach { - pollDao.delete(it) + val pollsMoreThanOneYearPast = polls.filter { + it.deadline?.isBefore(LocalDate.from(LocalDate.now().minusYears(1))) == true } - } + pollsMoreThanOneYearPast.forEach { poll -> + val pollDto = Poll() + pollDto.copyFrom(poll) + pollDto.fullAccessUsers?.forEach { user -> + if (user.email != null) { + pollMailService.sendPollDeletedMail(user.email!!, pollDto) + } + } + + pollDao.internalMarkAsDeleted(poll) + } + } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt index 413d9e71dc..accb0d5a6b 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt @@ -12,19 +12,20 @@ class PollMailService { @Autowired private lateinit var sendMail: SendMail - - fun sendMail(to:String ,subject: String,content: String, mailAttachments: List?= null){ - val mail = Mail() - mail.subject = subject - mail.contentType = Mail.CONTENTTYPE_HTML - mail.setTo(to) - mail.content = content - sendMail.send(mail, attachments = mailAttachments) - + fun sendMail(to: String, subject: String, content: String, mailAttachments: List? = null) { + val mail = Mail() + mail.subject = subject + mail.contentType = Mail.CONTENTTYPE_HTML + mail.setTo(to) + mail.content = content + sendMail.send(mail, attachments = mailAttachments) } - - - - + fun sendPollDeletedMail(to: String, pollDto: Poll, mailAttachments: List? = null) { + val mail = Mail() + mail.setTo(to) + mail.subject = "Poll ${pollDto.title} deleted" + mail.content = "Poll ${pollDto.title} deleted. The Excel files are attached." + sendMail.send(mail, attachments = mailAttachments) + } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 4d05f46e3c..b45eeea501 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -141,6 +141,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { }?.let { it.responses = pollResponseDO.responses pollResponseDao.update(it) + pollDao.delete(pollResponseDO.poll) return ResponseEntity.ok( ResponseAction( targetType = TargetType.REDIRECT, @@ -148,7 +149,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { ) ) } - + pollDao.delete(pollResponseDO.poll) pollResponseDao.saveOrUpdate(pollResponseDO) return ResponseEntity.ok( ResponseAction( From a87fd874057e958c0a2af6dd33c7a9a1c1536d3b Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 17 May 2023 14:28:20 +0200 Subject: [PATCH 097/160] reload Owner and access --- .../projectforge/rest/poll/PollPageRest.kt | 68 +++++++++++++++---- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 8f5fdda23d..c6d9d09ff2 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -185,7 +185,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun onAfterSaveOrUpdate(request: HttpServletRequest, obj: PollDO, postData: PostData) { // add all attendees mails - val mailTo: ArrayList = ArrayList(postData.data.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) + val mailTo: ArrayList = + ArrayList(postData.data.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) val owner = userService.getUser(obj.owner?.id) val mailFrom = owner?.email.toString() val mailSubject: String @@ -245,7 +246,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.inputFields!!.add(question) dto.owner = userService.getUser(dto.owner?.id) return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto) + .addVariable("ui", createEditLayout(dto, userAccess)) ) } @@ -255,7 +257,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto: Poll, watchFieldsTriggered: Array? ): ResponseEntity { - val userAccess = UILayout.UserAccess() + val groupIds = dto.groupAttendees?.filter { it.id != null }?.map { it.id!! }?.toIntArray() val userIds = UserService().getUserIds(groupService.getGroupUsers(groupIds)) val users = User.toUserList(userIds) @@ -272,6 +274,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.groupAttendees = mutableListOf() dto.attendees = allUsers.sortedBy { it.displayName } + dto.owner = userService.getUser(dto.owner?.id) + val userAccess = getUserAccess(dto) return ResponseEntity.ok( ResponseAction( @@ -295,7 +299,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto) + .addVariable("ui", createEditLayout(dto, userAccess)) ) } @@ -306,11 +311,25 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. if (!dto.isAlreadyCreated()) { fieldset.add(generateDeleteButton(layout, field.uid)) } - fieldset.add(getUiElement(dto.isAlreadyCreated(), "inputFields[${index}].question", translateMsg("poll.question"))) + fieldset.add( + getUiElement( + dto.isAlreadyCreated(), + "inputFields[${index}].question", + translateMsg("poll.question") + ) + ) if (field.type == BaseType.SingleResponseQuestion || field.type == BaseType.MultiResponseQuestion) { field.answers?.forEachIndexed { answerIndex, _ -> - fieldset.add(generateSingleAndMultiResponseAnswer(dto.isAlreadyCreated(), index, field.uid, answerIndex, layout)) + fieldset.add( + generateSingleAndMultiResponseAnswer( + dto.isAlreadyCreated(), + index, + field.uid, + answerIndex, + layout + ) + ) } if (!dto.isAlreadyCreated()) { fieldset.add( @@ -355,7 +374,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. UIButton.createDangerButton( id = "X", responseAction = ResponseAction( - "${Rest.URL}/poll/deleteAnswer/${questionUid}/${answerIndex}", targetType = TargetType.POST + "${Rest.URL}/poll/deleteAnswer/${questionUid}/${answerIndex}", + targetType = TargetType.POST ) ).withConfirmMessage(layout, confirmMessage = "poll.confirmation.deleteAnswer") ) @@ -378,7 +398,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.inputFields?.find { it.uid.equals(questionUid) }?.answers?.removeAt(answerIndex) dto.owner = userService.getUser(dto.owner?.id) return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto) + .addVariable("ui", createEditLayout(dto, userAccess)) ) } @@ -415,7 +436,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.inputFields?.remove(matchingQuestion) return ResponseEntity.ok( - ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable("ui", createEditLayout(dto, userAccess)) + ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto) + .addVariable("ui", createEditLayout(dto, userAccess)) ) } @@ -441,7 +463,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. /** * Once created, questions should be ReadOnly */ - private fun getUiElement(obj: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement { + private fun getUiElement( + obj: Boolean, + id: String, + label: String? = null, + dataType: UIDataType = UIDataType.STRING + ): UIElement { return if (obj) UIReadOnlyField(id, label = label, dataType = dataType) else @@ -454,14 +481,25 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. .add(lc, "title", "description", "location") .add(UISelect.createUserSelect(lc, "owner", false, "poll.owner")) .add(lc, "deadline", "date") - } - else { + } else { fieldset .add(UIReadOnlyField(value = pollDto.title, label = "titel", dataType = UIDataType.STRING)) .add(UIReadOnlyField(value = pollDto.description, label = "description", dataType = UIDataType.STRING)) .add(UIReadOnlyField(value = pollDto.location, label = "location", dataType = UIDataType.STRING)) - .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = "deadline", dataType = UIDataType.STRING)) - .add(UIReadOnlyField(value = (pollDto.date?.toString() ?: ""), label = "date", dataType = UIDataType.STRING)) + .add( + UIReadOnlyField( + value = pollDto.deadline.toString(), + label = "deadline", + dataType = UIDataType.STRING + ) + ) + .add( + UIReadOnlyField( + value = (pollDto.date?.toString() ?: ""), + label = "date", + dataType = UIDataType.STRING + ) + ) .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = "owner", dataType = UIDataType.STRING)) } } @@ -483,7 +521,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. UILayout.UserAccess(insert = true, update = true, delete = false, history = true) } else { // full access when viewing old poll - UILayout.UserAccess(insert = false, update = false, delete = true, history = false) + UILayout.UserAccess(insert = false, update = false, delete = true, history = false) } } } From becf6c5012650112b75352d8dac558b9241256d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Wed, 17 May 2023 14:38:45 +0200 Subject: [PATCH 098/160] Added check for export button --- .../kotlin/org/projectforge/rest/poll/Poll.kt | 6 ++- .../projectforge/rest/poll/PollCronJobs.kt | 1 - .../projectforge/rest/poll/PollPageRest.kt | 15 ++++++++ .../rest/poll/ResponsePageRest.kt | 38 +++++++++++++------ 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index 7d0a06cc85..8d17b70409 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -4,9 +4,9 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.projectforge.business.poll.PollDO import org.projectforge.framework.persistence.user.entities.PFUserDO import org.projectforge.rest.dto.BaseDTO -import org.projectforge.rest.poll.types.Question import org.projectforge.rest.dto.Group import org.projectforge.rest.dto.User +import org.projectforge.rest.poll.types.Question import java.time.LocalDate class Poll( @@ -50,4 +50,8 @@ class Poll( fun isAlreadyCreated(): Boolean { return id != null } + + fun isFinished(): Boolean { + return state == PollDO.State.FINISHED + } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt index 6e48011296..d4f73fa94e 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt @@ -43,7 +43,6 @@ class PollCronJobs { * Method to end polls after deadline */ private fun cronEndPolls() { - val pollDOs = pollDao.internalLoadAll() // set State.FINISHED for all old polls and export excel pollDOs.forEach { pollDO -> diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 481d1aca50..b92ad5f0c6 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -463,6 +463,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } +<<<<<<< Updated upstream /** * Once created, questions should be ReadOnly */ @@ -476,8 +477,22 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. UIReadOnlyField(id, label = label, dataType = dataType) else UIInput(id, label = label, dataType = dataType) +======= + companion object { + /** + * Once created, questions should be ReadOnly + */ + @JvmStatic + fun getUiElement(isReadOnly: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement { + return if (isReadOnly) + UIReadOnlyField(id, label = label, dataType = dataType) + else + UIInput(id, label = label, dataType = dataType) + } +>>>>>>> Stashed changes } + private fun addDefaultParameterFields(pollDto: Poll, fieldset: UIFieldset, isRunning: Boolean) { if (isRunning) { fieldset diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index c5e443e3e7..02f60a5052 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -50,17 +50,10 @@ class ResponsePageRest : AbstractDynamicPageRest() { val layout = UILayout("poll.response.title") val fieldset = UIFieldset(12, title = pollDto.title) - if (pollDto.state == PollDO.State.RUNNING && pollDto.isAlreadyCreated()) { + + + if (pollDto.isFinished() == false && pollDto.isAlreadyCreated() && pollDao.hasFullAccess(pollData)) { fieldset.add( - UIButton.createDefaultButton( - id = "response-poll-button", - responseAction = ResponseAction( - PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "${pollDto.id}", - targetType = TargetType.REDIRECT - ), - title = "poll.response.page" - ) - ).add( UIButton.createExportButton( id = "export-poll-response-button", responseAction = ResponseAction("${Rest.URL}/poll/export/${pollDto.id}", targetType = TargetType.POST), @@ -127,9 +120,32 @@ class ResponsePageRest : AbstractDynamicPageRest() { val col = UICol() if (field.type == BaseType.TextQuestion) { - col.add(UITextArea("responses[$index].answers[0]")) + col.add( + PollPageRest.getUiElement( + pollDto.isFinished(), + "responses[$index].answers[0]", + "poll.question.textQuestion", + UIDataType.STRING + ) + ) } if (field.type == BaseType.SingleResponseQuestion) { + col.add( + PollPageRest.getUiElement( + pollDto.isFinished(), + "responses[$index].answers[0]", + "poll.question.textQuestion", + UIDataType.BOOLEAN + ) + ) + col.add( + PollPageRest.getUiElement( + pollDto.isFinished(), + "responses[$index].answers[0]", + "poll.question.textQuestion", + UIDataType.BOOLEAN + ) + ) col.add( UIRadioButton( "responses[$index].answers[0]", value = field.answers!![0], label = field.answers?.get(0) ?: "" From a0d69acd485748f4aa5fbfad020875a2c8620b1e Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 17 May 2023 15:44:53 +0200 Subject: [PATCH 099/160] rename changeStateToFinsish --- .../main/kotlin/org/projectforge/rest/poll/PollPageRest.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 83247f1af8..e3e3be27b4 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -9,7 +9,6 @@ import org.projectforge.framework.access.AccessException import org.projectforge.framework.i18n.translateMsg import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext -import org.projectforge.model.rest.RestPaths import org.projectforge.rest.config.Rest import org.projectforge.rest.config.RestUtils import org.projectforge.rest.core.* @@ -175,7 +174,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. }?.confirmMessage = confirmMessage - if (getId(dto) != null) { + if (dto.isAlreadyCreated()) { processedLayout.addAction( UIButton.createDangerButton( id = "poll-button-finish", @@ -195,7 +194,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @PutMapping("/finish") - fun changeStatToFinish( + fun changeStateToFinsish( request: HttpServletRequest, @RequestBody postData: PostData ): ResponseEntity { From fa07db01982cd0e7eeb3d8b68e9c10d852553ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 30 May 2023 10:21:49 +0200 Subject: [PATCH 100/160] Fixed failed merge --- .../org/projectforge/rest/poll/PollPageRest.kt | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index c2d449f2d0..c305e631c6 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -289,7 +289,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto: Poll, watchFieldsTriggered: Array? ): ResponseEntity { - + val groupIds = dto.groupAttendees?.filter { it.id != null }?.map { it.id!! }?.toIntArray() val userIds = UserService().getUserIds(groupService.getGroupUsers(groupIds)) val users = User.toUserList(userIds) @@ -493,21 +493,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } -<<<<<<< Updated upstream - /** - * Once created, questions should be ReadOnly - */ - private fun getUiElement( - obj: Boolean, - id: String, - label: String? = null, - dataType: UIDataType = UIDataType.STRING - ): UIElement { - return if (obj) - UIReadOnlyField(id, label = label, dataType = dataType) - else - UIInput(id, label = label, dataType = dataType) -======= companion object { /** * Once created, questions should be ReadOnly @@ -519,7 +504,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. else UIInput(id, label = label, dataType = dataType) } ->>>>>>> Stashed changes } From 8c24d33de02f52c51c99a508105965ebf8ece1bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 30 May 2023 11:30:02 +0200 Subject: [PATCH 101/160] Fixed variable naming --- .../org/projectforge/business/poll/PollDO.kt | 4 ++-- .../org/projectforge/rest/poll/PollPageRest.kt | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 3333efbf53..cf5b7b6d3a 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -49,8 +49,8 @@ open class PollDO : DefaultBaseDO() { open var attendeeIds: String? = null @PropertyInfo(i18nKey = "poll.attendee_groups") - @get:Column(name = "groupAttendeesIds", nullable = true) - open var groupAttendeesIds: String? = null + @get:Column(name = "groupAttendeeIds", nullable = true) + open var groupAttendeeIds: String? = null @PropertyInfo(i18nKey = "poll.full_access_groups") @get:Column(name = "full_access_group_ids", length = 4000, nullable = true) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index a97be7da53..32d3fc1c32 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -2,15 +2,15 @@ package org.projectforge.rest.poll import com.fasterxml.jackson.databind.ObjectMapper import org.projectforge.business.group.service.GroupService -import org.projectforge.business.poll.PollDO -import org.projectforge.business.poll.PollDao +import org.projectforge.business.poll.* import org.projectforge.business.user.service.UserService import org.projectforge.framework.access.AccessException +import org.projectforge.framework.i18n.translate import org.projectforge.framework.i18n.translateMsg import org.projectforge.framework.persistence.api.MagicFilter -import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.persistence.api.QueryFilter import org.projectforge.framework.persistence.api.impl.CustomResultFilter +import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.rest.config.Rest import org.projectforge.rest.config.RestUtils import org.projectforge.rest.core.* @@ -20,11 +20,11 @@ import org.projectforge.rest.poll.types.BaseType import org.projectforge.rest.poll.types.PREMADE_QUESTIONS import org.projectforge.rest.poll.types.Question import org.projectforge.ui.* +import org.projectforge.ui.filter.UIFilterListElement import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.core.io.Resource -import org.projectforge.ui.filter.UIFilterListElement import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import java.time.LocalDate @@ -578,15 +578,15 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. /** * restricts the user access accordingly */ - private fun getUserAccess(pollDto: Poll): UILayout.UserAccess { + private fun getUserAccess(dto: Poll): UILayout.UserAccess { val pollDO = PollDO() - pollDto.copyTo(pollDO) + dto.copyTo(pollDO) return if (pollDao.hasFullAccess(pollDO) == false) { // no full access user UILayout.UserAccess(insert = false, update = false, delete = false, history = false) } else { - if (pollDto.id == null) { + if (!dto.isAlreadyCreated()) { // full access when creating new poll UILayout.UserAccess(insert = true, update = true, delete = false, history = true) } else { From be2b8794cf4ea8c66b77a89ac7ebd7c550ad8c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 30 May 2023 13:25:09 +0200 Subject: [PATCH 102/160] Fixed i18n and other small details --- .../org/projectforge/business/poll/PollDO.kt | 2 -- .../main/resources/I18nResources.properties | 1 + .../resources/I18nResources_de.properties | 1 + .../projectforge/rest/poll/PollPageRest.kt | 8 ++++++- .../rest/poll/ResponsePageRest.kt | 22 +++++++++---------- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 17f88704b2..31baa6a0ca 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -1,8 +1,6 @@ package org.projectforge.business.poll import org.hibernate.search.annotations.Indexed -import org.hibernate.search.annotations.IndexedEmbedded -import org.projectforge.business.common.BaseUserGroupRightsDO import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.persistence.api.AUserRightId import org.projectforge.framework.persistence.entities.DefaultBaseDO diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index 67e0403b0d..85f5bfcf6f 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -1984,6 +1984,7 @@ poll.confirmation.deleteAnswer=Do you really want to delete this answer? poll.confirmation.deleteQuestion=Do you really want to delete this question? poll.date=Date poll.deadline=Deadline +poll.delegationUser=Respond for another user poll.description=Description poll.error.oneQuestionRequired=At least one question is required. poll.fullAccessGroups=Full Access Groups diff --git a/projectforge-business/src/main/resources/I18nResources_de.properties b/projectforge-business/src/main/resources/I18nResources_de.properties index 338d7432f1..d9650006c9 100644 --- a/projectforge-business/src/main/resources/I18nResources_de.properties +++ b/projectforge-business/src/main/resources/I18nResources_de.properties @@ -2076,6 +2076,7 @@ poll.confirmation.deleteAnswer=M poll.confirmation.deleteQuestion=Möchtest du diese Frage wirklich löschen? poll.date=Datum poll.deadline=Antwortfrist +poll.delegationUser=Für anderen Nutzer abstimmen poll.description=Beschreibung poll.error.oneQuestionRequired=Mindestens eine Frage ist erforderlich. poll.fullAccessGroups=Gruppen mit Vollzugriff diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 6d3fd4256b..e6e2575a58 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -102,7 +102,13 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) ) if(hasFullAccess(dto)){ - fieldset.add(UIInput(id = "delegationUser", label = "poll.delegation_label", dataType = UIDataType.USER)) + fieldset.add( + UIInput( + id = "delegationUser", + label = "poll.delegationUser", + dataType = UIDataType.USER + ) + ) } fieldset.add( UIButton.createExportButton( diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 6e6741eeef..e94fb0145d 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -1,12 +1,12 @@ package org.projectforge.rest.poll +import com.beust.jcommander.internal.Nullable import com.fasterxml.jackson.databind.ObjectMapper import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao import org.projectforge.business.poll.PollResponseDO import org.projectforge.business.poll.PollResponseDao import org.projectforge.business.user.service.UserService -import org.projectforge.framework.access.AccessChecker import org.projectforge.framework.access.AccessCheckerImpl.I18N_KEY_VIOLATION_USER_NOT_MEMBER_OF import org.projectforge.framework.access.AccessException import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext @@ -39,20 +39,19 @@ class ResponsePageRest : AbstractDynamicPageRest() { @Autowired private lateinit var userService: UserService - @Autowired - private lateinit var accesscheck: AccessChecker - @GetMapping("dynamic") - fun getForm(request: HttpServletRequest, @RequestParam("pollid") pollStringId: String?, @RequestParam("questionOwner") delUser: String?): FormLayoutData { + fun getForm( + request: HttpServletRequest, + @RequestParam("pollid") pollStringId: String?, + @RequestParam("questionOwner") @Nullable delegatedUser: String? + ): FormLayoutData { val id = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") - - //used to load awnsers, is an attendee chosen by a fullaccessuser in order to awnser for them or the Threadlocal User - var questionOwner: Int? = null - val pollData = pollDao.internalGetById(id) ?: PollDO() - if (delUser != "null" && pollDao.hasFullAccess(pollData) && pollDao.isAttendee(pollData, delUser?.toInt())) - questionOwner = delUser?.toInt() + // used to load answers, is an attendee chosen by a fullaccessuser in order to answer for them or the ThreadLocalUserContext.user + var questionOwner: Int? + if (delegatedUser != "null" && pollDao.hasFullAccess(pollData) && pollDao.isAttendee(pollData, delegatedUser?.toInt())) + questionOwner = delegatedUser?.toInt() else questionOwner = ThreadLocalUserContext.user?.id @@ -149,7 +148,6 @@ class ResponsePageRest : AbstractDynamicPageRest() { request: HttpServletRequest, @RequestBody postData: PostData, @RequestParam("questionOwner") questionOwner: Int? ): ResponseEntity? { - val questionOwner: Int? = questionOwner val pollResponseDO = PollResponseDO() postData.data.copyTo(pollResponseDO) From 0cbfc0c726cf171594fe2beede17c099e5510ec3 Mon Sep 17 00:00:00 2001 From: Jona511 <78072860+Jona511@users.noreply.github.com> Date: Tue, 30 May 2023 14:33:31 +0200 Subject: [PATCH 103/160] Revert "Add filter for overview Page" --- .../business/poll/PollAssignment.kt | 10 -- .../business/poll/PollAssignmentFilter.kt | 16 -- .../org/projectforge/business/poll/PollDO.kt | 54 +------ .../org/projectforge/business/poll/PollDao.kt | 6 +- .../business/poll/PollResponseDO.kt | 3 - .../projectforge/business/poll/PollState.kt | 10 -- .../business/poll/PollStatusFilter.kt | 10 -- .../migrate/common/V7.5.1.2__7.5.1.0-POLL.sql | 4 +- .../kotlin/org/projectforge/rest/poll/Poll.kt | 15 +- .../org/projectforge/rest/poll/PollJobs.kt | 148 ------------------ .../projectforge/rest/poll/PollMailService.kt | 7 - .../projectforge/rest/poll/PollPageRest.kt | 44 ------ .../rest/poll/ResponsePageRest.kt | 4 +- 13 files changed, 20 insertions(+), 311 deletions(-) delete mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignment.kt delete mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignmentFilter.kt delete mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollState.kt delete mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatusFilter.kt delete mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollJobs.kt diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignment.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignment.kt deleted file mode 100644 index d3929d5262..0000000000 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignment.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.projectforge.business.poll - -import org.projectforge.common.i18n.I18nEnum - -enum class PollAssignment(val key: String): I18nEnum { - OWNER("owner"), ACCESS("access"), ATTENDEE("attendee"), OTHER("other"); - - override val i18nKey: String - get() = ("pollAssignment.$key") -} \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignmentFilter.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignmentFilter.kt deleted file mode 100644 index 8c8675e283..0000000000 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollAssignmentFilter.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.projectforge.business.poll - -import org.projectforge.framework.persistence.api.impl.CustomResultFilter - -class PollAssignmentFilter(val values: List): CustomResultFilter { - - override fun match(list: MutableList, element: PollDO): Boolean { - - element.getPollAssignment().forEach { pollAssignment -> - if (values.contains(pollAssignment)) { - return true - } - } - return false - } -} \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index bacec14760..31baa6a0ca 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -4,14 +4,12 @@ import org.hibernate.search.annotations.Indexed import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.persistence.api.AUserRightId import org.projectforge.framework.persistence.entities.DefaultBaseDO -import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.persistence.user.entities.PFUserDO import org.springframework.context.annotation.DependsOn import java.time.LocalDate import javax.persistence.* -@Suppress("UNREACHABLE_CODE") @Entity @Indexed @Table(name = "t_poll") @@ -45,64 +43,28 @@ open class PollDO : DefaultBaseDO() { open var date: LocalDate? = null @PropertyInfo(i18nKey = "poll.attendees") - @get:Column(name = "attendeeIds") - open var attendeeIds: String? = null + @get:Column(name = "attendeesIds", nullable = true) + open var attendeesIds: String? = null @PropertyInfo(i18nKey = "poll.attendee_groups") - @get:Column(name = "groupAttendeeIds", nullable = true) - open var groupAttendeeIds: String? = null + @get:Column(name = "groupAttendeesIds", nullable = true) + open var groupAttendeesIds: String? = null @PropertyInfo(i18nKey = "poll.full_access_groups") - @get:Column(name = "full_access_group_ids", length = 4000) + @get:Column(name = "full_access_group_ids", length = 4000, nullable = true) open var fullAccessGroupIds: String? = null @PropertyInfo(i18nKey = "poll.full_access_user") - @get:Column(name = "full_access_user_ids", length = 4000) + @get:Column(name = "full_access_user_ids", length = 4000, nullable = true) open var fullAccessUserIds: String? = null @PropertyInfo(i18nKey = "poll.inputFields") - @get:Column(name = "inputFields", length = 1000) + @get:Column(name = "inputFields", nullable = true, length = 1000) open var inputFields: String? = null @PropertyInfo(i18nKey = "poll.state") @get:Column(name = "state", nullable = false) - open var state: State = State.RUNNING - - @Transient - fun getPollAssignment(): MutableList { - val currentUserId = ThreadLocalUserContext.userId!! - val assignmentList = mutableListOf() - if (currentUserId == this.owner?.id) { - assignmentList.add(PollAssignment.OWNER) - } - if (this.fullAccessUserIds != null) { - val accessUserIds = this.fullAccessUserIds!!.split(", ").map { it.toInt() }.toIntArray() - if (accessUserIds.contains(currentUserId)) { - assignmentList.add(PollAssignment.ACCESS) - } - } - if (this.attendeeIds != null) { - val attendeeUserIds = this.attendeeIds!!.split(", ").map { it.toInt() }.toIntArray() - if (attendeeUserIds.contains(currentUserId)) { - assignmentList.add(PollAssignment.ATTENDEE) - } - } - if (assignmentList.isEmpty()) - assignmentList.add(PollAssignment.OTHER) - - return assignmentList - } - - @Transient - fun getPollStatus(): PollState { - //TODO: Maybe change this to enum class State - - return if (this.state == State.FINISHED) { - PollState.FINISHED - } else { - PollState.RUNNING - } - } + open var state: State? = State.RUNNING enum class State { RUNNING, FINISHED diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index 8ef88a1d4f..d9e9a6f518 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -55,15 +55,15 @@ open class PollDao : BaseDao(PollDO::class.java) { return false } - private fun isAttendee(obj: PollDO): Boolean { + fun isAttendee(obj: PollDO): Boolean { val loggedInUser = user - val listOfAttendeesIds = ObjectMapper().readValue(obj.attendeeIds, IntArray::class.java) + val listOfAttendeesIds = ObjectMapper().readValue(obj.attendeesIds, IntArray::class.java) if (loggedInUser != null) { if (listOfAttendeesIds.contains(loggedInUser.id)) { return true } } - obj.attendeeIds = ObjectMapper().writeValueAsString(listOfAttendeesIds) + obj.attendeesIds = ObjectMapper().writeValueAsString(listOfAttendeesIds) return false diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt index ccfbb9312f..2775557ec7 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt @@ -1,7 +1,5 @@ package org.projectforge.business.poll -import org.hibernate.annotations.OnDelete -import org.hibernate.annotations.OnDeleteAction import org.hibernate.search.annotations.Indexed import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.persistence.api.AUserRightId @@ -19,7 +17,6 @@ open class PollResponseDO : DefaultBaseDO() { @get:PropertyInfo(i18nKey = "poll.response.poll") @get:ManyToOne(fetch = FetchType.LAZY) - @get:OnDelete(action = OnDeleteAction.CASCADE) @get:JoinColumn(name = "poll_fk", nullable = false) open var poll: PollDO? = null diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollState.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollState.kt deleted file mode 100644 index 6f752abfa2..0000000000 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollState.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.projectforge.business.poll - -import org.projectforge.common.i18n.I18nEnum - -enum class PollState(val key: String): I18nEnum { - RUNNING("running"), FINISHED("finished"); - - override val i18nKey: String - get() = "poll.$key" -} \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatusFilter.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatusFilter.kt deleted file mode 100644 index 468461d4d5..0000000000 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollStatusFilter.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.projectforge.business.poll - -import org.projectforge.framework.persistence.api.impl.CustomResultFilter - -class PollStatusFilter(val values: List): CustomResultFilter { - - override fun match(list: MutableList, element: PollDO): Boolean { - return values.contains(element.getPollStatus()) - } -} \ No newline at end of file diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql index 9dfe517580..c16b83fe96 100644 --- a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql @@ -13,8 +13,8 @@ CREATE TABLE T_POLL deadline DATE NOT NULL, date DATE, state CHARACTER VARYING(1000) NOT NULL, - attendeeIds VARCHAR(5000), - groupAttendeeIds VARCHAR(5000), + attendeesIds VARCHAR(5000), + groupAttendeesIds VARCHAR(5000), full_access_user_ids CHARACTER VARYING(255), full_access_group_ids CHARACTER VARYING(255), inputFields CHARACTER Varying(100000) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index 1174c9337a..8d17b70409 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -22,31 +22,26 @@ class Poll( var fullAccessGroups: List? = null, var fullAccessUsers: List? = null, var groupAttendees: List? = null, - var attendees: List? = null, - private var frontendState: String? = null + var attendees: List? = null ) : BaseDTO() { override fun copyFrom(src: PollDO) { super.copyFrom(src) fullAccessGroups = Group.toGroupList(src.fullAccessGroupIds) fullAccessUsers = User.toUserList(src.fullAccessUserIds) - groupAttendees = Group.toGroupList(src.groupAttendeeIds) - attendees = User.toUserList(src.attendeeIds) + groupAttendees = Group.toGroupList(src.groupAttendeesIds) + attendees = User.toUserList(src.attendeesIds) if (src.inputFields != null) { val fields = ObjectMapper().readValue(src.inputFields, MutableList::class.java) inputFields = fields.map { Question().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() } - frontendState = if (state == PollDO.State.RUNNING) - "Endet am $deadline" - else - "Beendet" } override fun copyTo(dest: PollDO) { super.copyTo(dest) dest.fullAccessGroupIds = Group.toIntList(fullAccessGroups) dest.fullAccessUserIds = User.toIntList(fullAccessUsers) - dest.groupAttendeeIds = Group.toIntList(groupAttendees) - dest.attendeeIds = User.toIntList(attendees) + dest.groupAttendeesIds = Group.toIntList(groupAttendees) + dest.attendeesIds = User.toIntList(attendees) if (inputFields != null) { dest.inputFields = ObjectMapper().writeValueAsString(inputFields) } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollJobs.kt deleted file mode 100644 index 464c4f54e6..0000000000 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollJobs.kt +++ /dev/null @@ -1,148 +0,0 @@ -package org.projectforge.rest.poll - -import org.projectforge.business.poll.PollDO -import org.projectforge.business.poll.PollDao -import org.projectforge.business.user.UserDao -import org.projectforge.mail.MailAttachment -import org.projectforge.rest.poll.Exel.ExcelExport -import org.springframework.beans.factory.annotation.Autowired -import java.util.* -import org.springframework.scheduling.annotation.Scheduled -import org.springframework.web.bind.annotation.RestController -import java.time.LocalDate -import java.time.LocalDateTime -import java.time.ZoneId -import java.time.temporal.ChronoUnit -import kotlin.collections.ArrayList -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -@RestController -class CronJobs { - - private val log: Logger = LoggerFactory.getLogger(CronJobs::class.java) - - @Autowired - private lateinit var pollDao: PollDao - - @Autowired - private lateinit var pollMailService: PollMailService - - @Autowired - private lateinit var userDao: UserDao - - - /** - * Cron job for daily stuff - */ -// @Scheduled(cron = "0 0 1 * * *") // 1am everyday - @Scheduled(cron = "0 * * * * *") // Every Minute - fun dailyCronJobs() { - cronDeletePolls() - cronEndPolls() - } - - /** - * Method to end polls after deadline - */ - fun cronEndPolls() { - var mail = ""; - var header = ""; - - val polls = pollDao.internalLoadAll() - val list = ArrayList() - // set State.FINISHED for all old polls - polls.forEach { - if (it.deadline?.isBefore(LocalDate.now()) == true) { - it.state = PollDO.State.FINISHED - // check if state is open or closed - - val ihkExporter = ExcelExport() - - val poll = Poll() - poll.copyFrom(it) - - val exel = ihkExporter - .getExcel(poll) - - val attachment = object : MailAttachment { - override fun getFilename(): String { - return it.title + "_" + LocalDateTime.now().year + "_Result" + ".xlsx" - } - - override fun getContent(): ByteArray? { - return exel - } - } - list.add(attachment) - - header = "Umfrage ist abgelaufen" - mail = """ - Die Umfrage ist zu ende. Hier die ergebnisse. - """.trimMargin() - - pollDao.internalSaveOrUpdate(it) - } - - } - - try { - - // erstell mir eine funktion, die alles deadlines mir gibt die in der zukunft liegen - val pollsInFuture = polls.filter { it.deadline?.isAfter(LocalDate.now()) ?: false } - pollsInFuture.forEach { - val daysDifference = ChronoUnit.DAYS.between(LocalDate.now(), it.deadline) - if (daysDifference == 1L || daysDifference == 7L) { - header = "Umfrage Endet in $daysDifference Tage" - mail = """ - Sehr geehrter Teilnehmer,wir laden Sie herzlich dazu ein, an unserer Umfrage zum Thema ${it.title} teilzunehmen. - Ihre Meinung ist uns sehr wichtig und wir würden uns freuen, wenn Sie uns dabei helfen könnten, - unsere Forschungsergebnisse zu verbessern. Für diese Umfrage ist ${ - it ///owner - }zuständig. - Bei Fragen oder Anmerkungen können Sie sich gerne an ihn wenden. - Bitte beachten Sie, dass das Enddatum für die Teilnahme an dieser Umfrage der ${it.deadline.toString()} ist. - Wir würden uns freuen, wenn Sie sich die Zeit nehmen könnten, um diese Umfrage auszufüllen. - Vielen Dank im Voraus für Ihre Unterstützung. - - Mit freundlichen Grüßen,${ - it ///owmer - } - """.trimMargin() - } - } - - - if (mail.isNotEmpty()) { - pollMailService.sendMail(to = "test", subject = header, content = mail, mailAttachments = list) - } - } catch (e: Exception) { - log.error(e.toString()) - } - } - - - /** - * Method to delete old polls - */ - private fun cronDeletePolls() { - println("CRON JOB NOW!") - // check if poll end in future - val polls = pollDao.internalLoadAll() - val pollsMoreThanOneYearPast = polls.filter { - it.deadline?.isBefore(LocalDate.from(LocalDate.now().minusYears(1))) == true - } - - pollsMoreThanOneYearPast.forEach { poll -> - val pollDto = Poll() - pollDto.copyFrom(poll) - pollDto.fullAccessUsers?.forEach { user -> - if (user.email != null) { - pollMailService.sendPollDeletedMail(user.email!!, pollDto) - } - } - - pollDao.internalMarkAsDeleted(poll) - } - } -} \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt index 38eca366e2..38bc3d6b17 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt @@ -30,14 +30,7 @@ class PollMailService { } catch (e: Exception) { log.error(e.toString()) } - } - fun sendPollDeletedMail(to: String, pollDto: Poll, mailAttachments: List? = null) { - val mail = Mail() - mail.setTo(to) - mail.subject = "Poll ${pollDto.title} deleted" - mail.content = "Poll ${pollDto.title} deleted. The Excel files are attached." - sendMail.send(mail, attachments = mailAttachments) } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 2e699205ec..c305e631c6 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -5,15 +5,10 @@ import org.projectforge.business.group.service.GroupService import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao import org.projectforge.business.user.service.UserService -import org.projectforge.business.poll.* -import org.projectforge.framework.i18n.translate import org.projectforge.framework.access.AccessException import org.projectforge.framework.i18n.translateMsg import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext -import org.projectforge.menu.MenuItem -import org.projectforge.framework.persistence.api.QueryFilter -import org.projectforge.framework.persistence.api.impl.CustomResultFilter import org.projectforge.rest.config.Rest import org.projectforge.rest.config.RestUtils import org.projectforge.rest.core.* @@ -27,7 +22,6 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.core.io.Resource -import org.projectforge.ui.filter.UIFilterListElement import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import java.time.LocalDate @@ -99,11 +93,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. UITable.createUIResultSetTable() .add(pollLC, "title", "description", "location", "owner", "deadline", "date", "state") ) - .add(lc, "title", "description", "location", "owner", "deadline", "date") - .add(UIAgGridColumnDef.createCol( - field = "frontendState", - headerName = "State" - )) } @@ -294,39 +283,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) } - override fun addMagicFilterElements(elements: MutableList) { - elements.add( - UIFilterListElement("assignment", label = translate("poll.pollAssignment"), defaultFilter = true) - .buildValues(PollAssignment.OWNER, PollAssignment.ACCESS, PollAssignment.ATTENDEE, PollAssignment.OTHER) - ) - elements.add( - UIFilterListElement("status", label = translate("poll.status"), defaultFilter = true) - .buildValues(PollState.RUNNING, PollState.FINISHED) - ) - } - - override fun preProcessMagicFilter(target: QueryFilter, source: MagicFilter): List> { - val filters = mutableListOf>() - val assignmentFilterEntry = source.entries.find { it.field == "assignment" } - if (assignmentFilterEntry != null) { - assignmentFilterEntry.synthetic = true - val values = assignmentFilterEntry.value.values - if (!values.isNullOrEmpty()) { - val enums = values.map { PollAssignment.valueOf(it) } - filters.add(PollAssignmentFilter(enums)) - } - } - val statusFilterEntry = source.entries.find { it.field == "status" } - if (statusFilterEntry != null) { - statusFilterEntry.synthetic = true - val values = statusFilterEntry.value.values - if (!values.isNullOrEmpty()) { - val enums = values.map { PollState.valueOf(it) } - filters.add(PollStatusFilter(enums)) - } - } - return filters - } override fun onWatchFieldsUpdate( request: HttpServletRequest, diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 23d7373d69..02f60a5052 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -198,14 +198,14 @@ class ResponsePageRest : AbstractDynamicPageRest() { }?.let { it.responses = pollResponseDO.responses pollResponseDao.update(it) - pollDao.delete(pollResponseDO.poll) return ResponseEntity.ok( ResponseAction( targetType = TargetType.REDIRECT, url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) ) ) } - pollDao.delete(pollResponseDO.poll) + + pollResponseDao.saveOrUpdate(pollResponseDO) return ResponseEntity.ok( From 70c983a5ff1719dfafaab6bfb153df3ab58ee0d1 Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Wed, 31 May 2023 08:49:02 +0200 Subject: [PATCH 104/160] Redo the filter for overview page. --- .../org/projectforge/business/poll/PollDO.kt | 40 ++++++++++++++++ .../business/poll/filter/PollAssignment.kt | 10 ++++ .../poll/filter/PollAssignmentFilter.kt | 17 +++++++ .../business/poll/filter/PollState.kt | 10 ++++ .../business/poll/filter/PollStateFilter.kt | 11 +++++ .../projectforge/rest/poll/PollPageRest.kt | 48 ++++++++++++++++++- 6 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignment.kt create mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignmentFilter.kt create mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollState.kt create mode 100644 projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollStateFilter.kt diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 31baa6a0ca..5b97c92fac 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -1,9 +1,12 @@ package org.projectforge.business.poll import org.hibernate.search.annotations.Indexed +import org.projectforge.business.poll.filter.PollAssignment +import org.projectforge.business.poll.filter.PollState import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.persistence.api.AUserRightId import org.projectforge.framework.persistence.entities.DefaultBaseDO +import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.persistence.user.entities.PFUserDO import org.springframework.context.annotation.DependsOn import java.time.LocalDate @@ -66,6 +69,43 @@ open class PollDO : DefaultBaseDO() { @get:Column(name = "state", nullable = false) open var state: State? = State.RUNNING + @Transient + fun getPollAssignment(): MutableList { + val currentUserId = ThreadLocalUserContext.userId!! + val assignmentList = mutableListOf() + if (currentUserId == this.owner?.id) { + assignmentList.add(PollAssignment.OWNER) + } + if (this.fullAccessUserIds != null) { + val accessUserIds = this.fullAccessUserIds!!.split(", ").map { it.toInt() }.toIntArray() + if (accessUserIds.contains(currentUserId)) { + assignmentList.add(PollAssignment.ACCESS) + } + } + if (this.attendeesIds != null) { + val attendeeUserIds = this.attendeesIds!!.split(", ").map { it.toInt() }.toIntArray() + if (attendeeUserIds.contains(currentUserId)) { + assignmentList.add(PollAssignment.ATTENDEE) + } + } + if (assignmentList.isEmpty()) + assignmentList.add(PollAssignment.OTHER) + + return assignmentList + } + + @Transient + fun getPollStatus(): PollState { + //TODO: Maybe change this to enum class State + + return if (this.state == State.FINISHED) { + PollState.FINISHED + } else { + PollState.RUNNING + } + } + + enum class State { RUNNING, FINISHED } diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignment.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignment.kt new file mode 100644 index 0000000000..41b3b69611 --- /dev/null +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignment.kt @@ -0,0 +1,10 @@ +package org.projectforge.business.poll.filter + +import org.projectforge.common.i18n.I18nEnum + +enum class PollAssignment(val key: String) : I18nEnum { + OWNER("owner"), ACCESS("access"), ATTENDEE("attendee"), OTHER("other"); + + override val i18nKey: String + get() = ("pollAssignment.$key") +} \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignmentFilter.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignmentFilter.kt new file mode 100644 index 0000000000..c452b2d8b9 --- /dev/null +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignmentFilter.kt @@ -0,0 +1,17 @@ +package org.projectforge.business.poll.filter + +import org.projectforge.business.poll.PollDO +import org.projectforge.framework.persistence.api.impl.CustomResultFilter + +class PollAssignmentFilter(val values: List) : CustomResultFilter { + + override fun match(list: MutableList, element: PollDO): Boolean { + + element.getPollAssignment().forEach { pollAssignment -> + if (values.contains(pollAssignment)) { + return true + } + } + return false + } +} \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollState.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollState.kt new file mode 100644 index 0000000000..0fb16459d8 --- /dev/null +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollState.kt @@ -0,0 +1,10 @@ +package org.projectforge.business.poll.filter + +import org.projectforge.common.i18n.I18nEnum + +enum class PollState(val key: String) : I18nEnum { + RUNNING("running"), FINISHED("finished"); + + override val i18nKey: String + get() = "poll.$key" +} \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollStateFilter.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollStateFilter.kt new file mode 100644 index 0000000000..b813c71b99 --- /dev/null +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollStateFilter.kt @@ -0,0 +1,11 @@ +package org.projectforge.business.poll.filter; + +import org.projectforge.business.poll.PollDO +import org.projectforge.framework.persistence.api.impl.CustomResultFilter + +class PollStateFilter(val values: List) : CustomResultFilter { + + override fun match(list: MutableList, element: PollDO): Boolean { + return values.contains(element.getPollStatus()) + } +} diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index c305e631c6..c7b49e18ce 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -4,10 +4,17 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.projectforge.business.group.service.GroupService import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao +import org.projectforge.business.poll.filter.PollAssignment +import org.projectforge.business.poll.filter.PollAssignmentFilter +import org.projectforge.business.poll.filter.PollState +import org.projectforge.business.poll.filter.PollStateFilter import org.projectforge.business.user.service.UserService import org.projectforge.framework.access.AccessException +import org.projectforge.framework.i18n.translate import org.projectforge.framework.i18n.translateMsg import org.projectforge.framework.persistence.api.MagicFilter +import org.projectforge.framework.persistence.api.QueryFilter +import org.projectforge.framework.persistence.api.impl.CustomResultFilter import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.rest.config.Rest import org.projectforge.rest.config.RestUtils @@ -18,6 +25,7 @@ import org.projectforge.rest.poll.types.BaseType import org.projectforge.rest.poll.types.PREMADE_QUESTIONS import org.projectforge.rest.poll.types.Question import org.projectforge.ui.* +import org.projectforge.ui.filter.UIFilterListElement import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired @@ -95,6 +103,39 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) } + override fun addMagicFilterElements(elements: MutableList) { + elements.add( + UIFilterListElement("assignment", label = translate("poll.pollAssignment"), defaultFilter = true) + .buildValues(PollAssignment.OWNER, PollAssignment.OTHER) + ) + elements.add( + UIFilterListElement("status", label = translate("poll.state"), defaultFilter = true) + .buildValues(PollState.RUNNING, PollState.FINISHED) + ) + } + + override fun preProcessMagicFilter(target: QueryFilter, source: MagicFilter): List>? { + val filters = mutableListOf>() + val assignmentFilterEntry = source.entries.find { it.field == "assignment" } + if (assignmentFilterEntry != null) { + assignmentFilterEntry.synthetic = true + val values = assignmentFilterEntry.value.values + if (!values.isNullOrEmpty()) { + val enums = values.map { PollAssignment.valueOf(it) } + filters.add(PollAssignmentFilter(enums)) + } + } + val statusFilterEntry = source.entries.find { it.field == "status" } + if (statusFilterEntry != null) { + statusFilterEntry.synthetic = true + val values = statusFilterEntry.value.values + if (!values.isNullOrEmpty()) { + val enums = values.map { PollState.valueOf(it) } + filters.add(PollStateFilter(enums)) + } + } + return filters + } override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { val layout = super.createEditLayout(dto, userAccess) @@ -498,7 +539,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. * Once created, questions should be ReadOnly */ @JvmStatic - fun getUiElement(isReadOnly: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement { + fun getUiElement( + isReadOnly: Boolean, + id: String, + label: String? = null, + dataType: UIDataType = UIDataType.STRING + ): UIElement { return if (isReadOnly) UIReadOnlyField(id, label = label, dataType = dataType) else From 3e8ce80fa8bc8b9fde1d1f209bbf5b6cfdaf67b6 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 31 May 2023 12:01:35 +0200 Subject: [PATCH 105/160] add mail as I18n and create sendMailResponseToOwner method --- .../main/resources/I18nResources.properties | 8 ++++ .../rest/poll/ResponsePageRest.kt | 37 +++++++++++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index e9cfd90bf1..c7f54754fd 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -2004,6 +2004,14 @@ poll.export.response.poll=Export results poll.respond=Send responses poll.response.page=Poll Response Page poll.response.title=Poll Response Page +poll.response.mail.update.subject=Response was edited by {0} +poll.response.mail.update.content=

Dear {0},

\ +

I wanted to inform you that Person {2} has updated their answer to Poll {1}.

\ +

If you were not notified about this,

\ +

I recommend reaching out to the person directly or adjusting your results accordingly.

\ +

You can modify your response until "{4}".

\ +

Best regards,

\ +

{2}

poll.mail.endingSoon.subject=Poll ending in {0} days poll.mail.endingSoon.content=

Dear Attendees,

\

This is a friendly reminder that the poll "{0}" created by {1} is ending soon, on {2}. Please make sure to submit your responses before the deadline.

\ diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 02f60a5052..e7be05839c 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -8,6 +8,7 @@ import org.projectforge.business.poll.PollResponseDao import org.projectforge.framework.access.AccessException import org.projectforge.framework.i18n.translateMsg import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext +import org.projectforge.framework.persistence.user.entities.PFUserDO import org.projectforge.framework.utils.NumberHelper import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType @@ -22,8 +23,10 @@ import org.projectforge.ui.* import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* +import java.time.format.DateTimeFormatter import java.util.* import javax.servlet.http.HttpServletRequest +import kotlin.collections.ArrayList @RestController @RequestMapping("${Rest.URL}/response") @@ -35,6 +38,9 @@ class ResponsePageRest : AbstractDynamicPageRest() { @Autowired private lateinit var pollResponseDao: PollResponseDao + @Autowired + private lateinit var pollMailService: PollMailService + @GetMapping("dynamic") fun getForm( request: HttpServletRequest, @RequestParam("id") pollStringId: String? @@ -56,7 +62,10 @@ class ResponsePageRest : AbstractDynamicPageRest() { fieldset.add( UIButton.createExportButton( id = "export-poll-response-button", - responseAction = ResponseAction("${Rest.URL}/poll/export/${pollDto.id}", targetType = TargetType.POST), + responseAction = ResponseAction( + "${Rest.URL}/poll/export/${pollDto.id}", + targetType = TargetType.POST + ), title = "poll.export.response.poll" ) ) @@ -200,7 +209,8 @@ class ResponsePageRest : AbstractDynamicPageRest() { pollResponseDao.update(it) return ResponseEntity.ok( ResponseAction( - targetType = TargetType.REDIRECT, url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + targetType = TargetType.REDIRECT, + url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) ) ) } @@ -208,9 +218,30 @@ class ResponsePageRest : AbstractDynamicPageRest() { pollResponseDao.saveOrUpdate(pollResponseDO) + if (ThreadLocalUserContext.user != pollResponseDO.owner) { + sendMailResponseOwner(pollResponseDO, ThreadLocalUserContext.user!!) + } + return ResponseEntity.ok( ResponseAction( - targetType = TargetType.REDIRECT, url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + targetType = TargetType.REDIRECT, + url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + ) + ) + } + + private fun sendMailResponseOwner(pollResponseDO: PollResponseDO, canedUser: PFUserDO) { + var emailList = ArrayList() + pollResponseDO.owner?.email?.let { emailList.add(it) } + pollMailService.sendMail( + canedUser?.email.toString(), emailList, + translateMsg("poll.response.mail.update.subject", canedUser.displayName), + translateMsg( + "poll.response.mail.update.content", + pollResponseDO.owner?.displayName, + pollResponseDO.poll?.title, + canedUser.displayName, + pollResponseDO.poll?.deadline?.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")).toString() ) ) } From b8fc6234101c24cf7fb773bc94a05ea5f71e54b8 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 31 May 2023 12:25:36 +0200 Subject: [PATCH 106/160] create sendMailResponseToOwener method --- .../kotlin/org/projectforge/rest/poll/ResponsePageRest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index e7be05839c..6613b428ab 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -219,9 +219,9 @@ class ResponsePageRest : AbstractDynamicPageRest() { pollResponseDao.saveOrUpdate(pollResponseDO) if (ThreadLocalUserContext.user != pollResponseDO.owner) { - sendMailResponseOwner(pollResponseDO, ThreadLocalUserContext.user!!) + sendMailResponseToOwner(pollResponseDO, ThreadLocalUserContext.user!!) } - + return ResponseEntity.ok( ResponseAction( targetType = TargetType.REDIRECT, @@ -230,7 +230,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { ) } - private fun sendMailResponseOwner(pollResponseDO: PollResponseDO, canedUser: PFUserDO) { + private fun sendMailResponseToOwner(pollResponseDO: PollResponseDO, canedUser: PFUserDO) { var emailList = ArrayList() pollResponseDO.owner?.email?.let { emailList.add(it) } pollMailService.sendMail( From ea335adb3bb38f0b29adbaf7427a158af6855634 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 31 May 2023 12:40:26 +0200 Subject: [PATCH 107/160] doppelte i18n keys entfernt --- .../main/resources/I18nResources.properties | 7 ---- .../projectforge/rest/poll/PollPageRest.kt | 40 +++++++++++++------ 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index a2a4e36909..e9cfd90bf1 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -1981,13 +1981,6 @@ poll.fullAccessUsers=Full Access User poll.fullAccessGroups=Full Access Groups poll.date=Date poll.deadline=Deadline -poll.delegationUser=Respond for another user -poll.description=Description -poll.error.oneQuestionRequired=At least one question is required. -poll.fullAccessGroups=Full Access Groups -poll.fullAccessUsers=Full Access User -poll.groupAttendees=Attendee Groups -poll.guide=Poll Guide poll.location=Location poll.state=State poll.infopage=Info Page diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 747bf9e5cf..a17b05fe78 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -82,7 +82,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. * @return the response page. */ override fun getStandardEditPage(): String { - return "${PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java)}:id" + return "${PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java)}" } override fun createListLayout( @@ -100,15 +100,23 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val layout = super.createEditLayout(dto, userAccess) val fieldset = UIFieldset(UILength(12)) if (dto.state == PollDO.State.RUNNING && dto.isAlreadyCreated()) { - fieldset.add(UIRow() - .add(UIButton.createDefaultButton( - id = "response-poll-button", - responseAction = ResponseAction(PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java, absolute = true) + "?pollid=${dto.id}&questionOwner=${dto.delegationUser?.id}", targetType = TargetType.REDIRECT), - title = "poll.response.page" - ), - ) + fieldset.add( + UIRow() + .add( + UIButton.createDefaultButton( + id = "response-poll-button", + responseAction = ResponseAction( + PagesResolver.getDynamicPageUrl( + ResponsePageRest::class.java, + absolute = true + ) + "?pollid=${dto.id}&questionOwner=${dto.delegationUser?.id}", + targetType = TargetType.GET + ), + title = "poll.response.page" + ), + ) ) - if(hasFullAccess(dto)){ + if (hasFullAccess(dto)) { fieldset.add( UIInput( id = "delegationUser", @@ -305,6 +313,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) ) } + // PostMapping add @PutMapping("/add") fun addQuestionField( @@ -548,7 +557,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. * Once created, questions should be ReadOnly */ @JvmStatic - fun getUiElement(isReadOnly: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement { + fun getUiElement( + isReadOnly: Boolean, + id: String, + label: String? = null, + dataType: UIDataType = UIDataType.STRING + ): UIElement { return if (isReadOnly) UIReadOnlyField(id, label = label, dataType = dataType) else @@ -608,12 +622,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } } - private fun hasFullAccess(dto: Poll): Boolean{ + private fun hasFullAccess(dto: Poll): Boolean { val loggedInUser = ThreadLocalUserContext.user val foundUser = dto.fullAccessUsers?.any { user -> user.id == loggedInUser?.id } - if(foundUser == true || dto.owner?.id == loggedInUser?.id) { + if (foundUser == true || dto.owner?.id == loggedInUser?.id) { return true } - return false + return false } } From 61ec61c97ca948449ef5316a2e8fda05e38ee094 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 31 May 2023 13:06:36 +0200 Subject: [PATCH 108/160] invert pollData.getPollAssignment() --- .../projectforge/rest/poll/ResponsePageRest.kt | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index b5511d985b..6a05746521 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -40,7 +40,10 @@ class ResponsePageRest : AbstractDynamicPageRest() { fun getForm( request: HttpServletRequest, @RequestParam("id") pollStringId: String? ): FormLayoutData { - val id = NumberHelper.parseInteger(pollStringId) ?: throw AccessException("access.exception.noAccess", "Umfrage nicht gefunden.") + val id = NumberHelper.parseInteger(pollStringId) ?: throw AccessException( + "access.exception.noAccess", + "Umfrage nicht gefunden." + ) val pollData = pollDao.internalGetById(id) ?: PollDO() val pollDto = transformPollFromDB(pollData) @@ -48,7 +51,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { throw AccessException("access.exception.noAccess", "Umfrage nicht gefunden.") } - if (pollData.getPollAssignment().contains(PollAssignment.ATTENDEE)) { + if (!pollData.getPollAssignment().contains(PollAssignment.ATTENDEE)) { throw AccessException("access.exception.noAccess", "Du darfst nicht auf diese Umfrage antworten.") } if (pollDto.state == PollDO.State.FINISHED) { @@ -64,7 +67,10 @@ class ResponsePageRest : AbstractDynamicPageRest() { fieldset.add( UIButton.createExportButton( id = "export-poll-response-button", - responseAction = ResponseAction("${Rest.URL}/poll/export/${pollDto.id}", targetType = TargetType.POST), + responseAction = ResponseAction( + "${Rest.URL}/poll/export/${pollDto.id}", + targetType = TargetType.POST + ), title = "poll.export.response.poll" ) ) @@ -208,7 +214,8 @@ class ResponsePageRest : AbstractDynamicPageRest() { pollResponseDao.update(it) return ResponseEntity.ok( ResponseAction( - targetType = TargetType.REDIRECT, url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + targetType = TargetType.REDIRECT, + url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) ) ) } @@ -218,7 +225,8 @@ class ResponsePageRest : AbstractDynamicPageRest() { return ResponseEntity.ok( ResponseAction( - targetType = TargetType.REDIRECT, url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + targetType = TargetType.REDIRECT, + url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) ) ) } From d4d545d26d4317ffb9f8b3a741fa2b6c0e17a23c Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 31 May 2023 13:41:54 +0200 Subject: [PATCH 109/160] samal fixes --- .../kotlin/org/projectforge/rest/poll/PollPageRest.kt | 8 ++++++-- .../kotlin/org/projectforge/rest/poll/ResponsePageRest.kt | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 32d3fc1c32..b32b20c2cf 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -321,7 +321,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } - override fun onWatchFieldsUpdate( request: HttpServletRequest, dto: Poll, @@ -536,7 +535,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. * Once created, questions should be ReadOnly */ @JvmStatic - fun getUiElement(isReadOnly: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement { + fun getUiElement( + isReadOnly: Boolean, + id: String, + label: String? = null, + dataType: UIDataType = UIDataType.STRING + ): UIElement { return if (isReadOnly) UIReadOnlyField(id, label = label, dataType = dataType) else diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 6a05746521..61681c6746 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -51,7 +51,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { throw AccessException("access.exception.noAccess", "Umfrage nicht gefunden.") } - if (!pollData.getPollAssignment().contains(PollAssignment.ATTENDEE)) { + if (pollData.getPollAssignment().contains(PollAssignment.OTHER)) { throw AccessException("access.exception.noAccess", "Du darfst nicht auf diese Umfrage antworten.") } if (pollDto.state == PollDO.State.FINISHED) { From 5e1f2def8968a50554f2bd1a18932d314d0e8d9e Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Wed, 31 May 2023 13:51:23 +0200 Subject: [PATCH 110/160] Cron job delete poll and pollResponses after 1 year. Delete poll and pollResponses after click on delete. --- .../projectforge/rest/poll/PollCronJobs.kt | 32 +++++++++---------- .../projectforge/rest/poll/PollPageRest.kt | 22 ++++++++++++- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt index d4f73fa94e..8cb3c04233 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt @@ -2,6 +2,7 @@ package org.projectforge.rest.poll import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao +import org.projectforge.business.poll.PollResponseDao import org.projectforge.framework.i18n.translateMsg import org.projectforge.mail.MailAttachment import org.projectforge.rest.poll.excel.ExcelExport @@ -10,7 +11,6 @@ import org.springframework.scheduling.annotation.Scheduled import org.springframework.web.bind.annotation.RestController import java.time.LocalDate import java.time.LocalDateTime -import java.time.ZoneId import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit import java.util.* @@ -18,10 +18,12 @@ import java.util.* @RestController class PollCronJobs { - @Autowired private lateinit var pollDao: PollDao + @Autowired + private lateinit var pollResponseDao: PollResponseDao + @Autowired private lateinit var pollMailService: PollMailService @@ -31,14 +33,13 @@ class PollCronJobs { /** * Cron job for daily stuff */ -// todo @Scheduled(cron = "0 0 1 * * *") // 1am everyday - @Scheduled(cron = "0 * * * * *") // 1am everyday + // todo @Scheduled(cron = "0 0 1 * * *") // 1am everyday + @Scheduled(cron = "0 * * * * *") // Every Minute fun dailyCronJobs() { cronDeletePolls() cronEndPolls() } - /** * Method to end polls after deadline */ @@ -96,25 +97,22 @@ class PollCronJobs { } } - /** * Method to delete old polls */ private fun cronDeletePolls() { - // check if poll end in future val polls = pollDao.internalLoadAll() val pollsMoreThanOneYearPast = polls.filter { - it.created?.before( - Date.from( - LocalDate.now().minusYears(1).atStartOfDay( - ZoneId.systemDefault() - ).toInstant() - ) - ) ?: false + it.deadline!!.isBefore(LocalDate.now().minusYears(1)) } - pollsMoreThanOneYearPast.forEach { - pollDao.delete(it) + pollsMoreThanOneYearPast.forEach { poll -> + val pollResponses = pollResponseDao.internalLoadAll().filter { response -> + response.poll!!.id == poll.id + } + pollResponses.forEach { + pollResponseDao.internalMarkAsDeleted(it) + } + pollDao.internalMarkAsDeleted(poll) } } - } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index c305e631c6..cc54de2bf1 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.projectforge.business.group.service.GroupService import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao +import org.projectforge.business.poll.PollResponseDao import org.projectforge.business.user.service.UserService import org.projectforge.framework.access.AccessException import org.projectforge.framework.i18n.translateMsg @@ -50,12 +51,25 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @Autowired private lateinit var excelExport: ExcelExport + @Autowired + private lateinit var pollResponseDao: PollResponseDao + override fun newBaseDTO(request: HttpServletRequest?): Poll { val result = Poll() result.owner = ThreadLocalUserContext.user return result } + override fun onBeforeMarkAsDeleted(request: HttpServletRequest, obj: PollDO, postData: PostData) { + val responsesToDelete = pollResponseDao.internalLoadAll().filter { + it.poll!!.id == obj.id + } + responsesToDelete.forEach { + pollResponseDao.markAsDeleted(it) + } + super.onBeforeMarkAsDeleted(request, obj, postData) + } + override fun transformForDB(dto: Poll): PollDO { val pollDO = PollDO() @@ -101,6 +115,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val fieldset = UIFieldset(UILength(12)) + addDefaultParameterFields(dto, fieldset, isRunning = dto.state == PollDO.State.RUNNING) fieldset @@ -498,7 +513,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. * Once created, questions should be ReadOnly */ @JvmStatic - fun getUiElement(isReadOnly: Boolean, id: String, label: String? = null, dataType: UIDataType = UIDataType.STRING): UIElement { + fun getUiElement( + isReadOnly: Boolean, + id: String, + label: String? = null, + dataType: UIDataType = UIDataType.STRING + ): UIElement { return if (isReadOnly) UIReadOnlyField(id, label = label, dataType = dataType) else From 012f0ed71f587d9ab9f067b00687a68f8203811b Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 31 May 2023 14:11:17 +0200 Subject: [PATCH 111/160] remove getUiElement --- .../rest/poll/ResponsePageRest.kt | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 02f60a5052..efcf2e5c41 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -56,7 +56,10 @@ class ResponsePageRest : AbstractDynamicPageRest() { fieldset.add( UIButton.createExportButton( id = "export-poll-response-button", - responseAction = ResponseAction("${Rest.URL}/poll/export/${pollDto.id}", targetType = TargetType.POST), + responseAction = ResponseAction( + "${Rest.URL}/poll/export/${pollDto.id}", + targetType = TargetType.POST + ), title = "poll.export.response.poll" ) ) @@ -130,22 +133,6 @@ class ResponsePageRest : AbstractDynamicPageRest() { ) } if (field.type == BaseType.SingleResponseQuestion) { - col.add( - PollPageRest.getUiElement( - pollDto.isFinished(), - "responses[$index].answers[0]", - "poll.question.textQuestion", - UIDataType.BOOLEAN - ) - ) - col.add( - PollPageRest.getUiElement( - pollDto.isFinished(), - "responses[$index].answers[0]", - "poll.question.textQuestion", - UIDataType.BOOLEAN - ) - ) col.add( UIRadioButton( "responses[$index].answers[0]", value = field.answers!![0], label = field.answers?.get(0) ?: "" @@ -200,7 +187,8 @@ class ResponsePageRest : AbstractDynamicPageRest() { pollResponseDao.update(it) return ResponseEntity.ok( ResponseAction( - targetType = TargetType.REDIRECT, url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + targetType = TargetType.REDIRECT, + url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) ) ) } @@ -210,7 +198,8 @@ class ResponsePageRest : AbstractDynamicPageRest() { return ResponseEntity.ok( ResponseAction( - targetType = TargetType.REDIRECT, url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + targetType = TargetType.REDIRECT, + url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) ) ) } From 4b5983b162d5ca24a7eb3467756b09e674bedb14 Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Thu, 1 Jun 2023 09:28:04 +0200 Subject: [PATCH 112/160] url reload funktioniert nicht --- .../projectforge/rest/poll/PollPageRest.kt | 19 +--- .../rest/poll/ResponsePageRest.kt | 102 ++++++++++++++---- 2 files changed, 82 insertions(+), 39 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index a17b05fe78..4a26fb85cb 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -82,7 +82,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. * @return the response page. */ override fun getStandardEditPage(): String { - return "${PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java)}" + return "${PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java)}?number=:id" } override fun createListLayout( @@ -116,15 +116,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ), ) ) - if (hasFullAccess(dto)) { - fieldset.add( - UIInput( - id = "delegationUser", - label = "poll.delegationUser", - dataType = UIDataType.USER - ) - ) - } fieldset.add( UIButton.createExportButton( id = "export-poll-response-button", @@ -622,12 +613,4 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } } - private fun hasFullAccess(dto: Poll): Boolean { - val loggedInUser = ThreadLocalUserContext.user - val foundUser = dto.fullAccessUsers?.any { user -> user.id == loggedInUser?.id } - if (foundUser == true || dto.owner?.id == loggedInUser?.id) { - return true - } - return false - } } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index c5884325ec..c787d2b3f3 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -12,12 +12,14 @@ import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.utils.NumberHelper import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType +import org.projectforge.model.rest.RestPaths import org.projectforge.rest.config.Rest import org.projectforge.rest.core.AbstractDynamicPageRest import org.projectforge.rest.core.PagesResolver import org.projectforge.rest.core.RestResolver import org.projectforge.rest.dto.FormLayoutData import org.projectforge.rest.dto.PostData +import org.projectforge.rest.dto.User import org.projectforge.rest.poll.types.* import org.projectforge.ui.* import org.springframework.beans.factory.annotation.Autowired @@ -25,6 +27,7 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import java.util.* import javax.servlet.http.HttpServletRequest +import javax.validation.Valid @RestController @@ -40,19 +43,22 @@ class ResponsePageRest : AbstractDynamicPageRest() { @Autowired private lateinit var userService: UserService + var pollId: Int? = null + var delegatedUser: User? = null @GetMapping("dynamic") - fun getForm( - request: HttpServletRequest, - @RequestParam("pollid") pollStringId: String?, - @RequestParam("questionOwner") delegatedUser: String? - ): FormLayoutData { - val id = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") - val pollData = pollDao.internalGetById(id) ?: PollDO() - - // used to load answers, is an attendee chosen by a fullaccessuser in order to answer for them or the ThreadLocalUserContext.user - var questionOwner: Int? - if (delegatedUser != "null" && pollDao.hasFullAccess(pollData) && pollDao.isAttendee(pollData, delegatedUser?.toInt())) - questionOwner = delegatedUser?.toInt() + fun getForm(request: HttpServletRequest, + @RequestParam("number") pollStringId: String?, + @RequestParam("delegatedUser") delUser: String?, + ): FormLayoutData { + pollId = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") + + //used to load awnsers, is an attendee chosen by a fullaccessuser in order to awnser for them or the Threadlocal User + var questionOwner: Int? = null + + val pollData = pollDao.internalGetById(pollId) ?: PollDO() + + if (delUser != null && pollDao.hasFullAccess(pollData) && pollDao.isAttendee(pollData, delUser.toInt())) + questionOwner = delUser.toInt() else questionOwner = ThreadLocalUserContext.user?.id @@ -64,16 +70,37 @@ class ResponsePageRest : AbstractDynamicPageRest() { } val layout = UILayout("poll.response.title") - val fieldset = UIFieldset(12, title = pollDto.title + " Antworten von " + questionOwnerName) - fieldset + val fieldSet = UIFieldset(12, title = pollDto.title + " Antworten von " + questionOwnerName) + if (hasFullAccess(pollDto)) { + fieldSet.add( + UIInput( + id = "delegationUser", + label = "poll.delegationUser", + dataType = UIDataType.USER + ) + ) + .add( + UIButton.createDefaultButton( + id = "response-poll-button", + responseAction = ResponseAction( + RestResolver.getRestUrl( + this::class.java, + "showDelegatedUser" + ), targetType = TargetType.PUT + ), + title = "poll.response.page" + ), + )} + fieldSet .add(UIReadOnlyField(value = pollDto.description, label = "Description")) .add(UIReadOnlyField(value = pollDto.location, label = "Location")) .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = "Owner")) .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = "Deadline")) + if (pollDto.isFinished() == false && pollDto.isAlreadyCreated() && pollDao.hasFullAccess(pollData)) { - fieldset.add( + fieldSet.add( UIButton.createExportButton( id = "export-poll-response-button", responseAction = ResponseAction("${Rest.URL}/poll/export/${pollDto.id}", targetType = TargetType.POST), @@ -81,7 +108,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { ) ) } - fieldset.add( + fieldSet.add( UIRow().add( UICol( UILength(10) @@ -101,12 +128,12 @@ class ResponsePageRest : AbstractDynamicPageRest() { ) ) - fieldset.add(UIReadOnlyField(value = pollDto.description, label = translateMsg("poll.description"))) + fieldSet.add(UIReadOnlyField(value = pollDto.description, label = translateMsg("poll.description"))) .add(UIReadOnlyField(value = pollDto.location, label = translateMsg("poll.location"))) .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = translateMsg("poll.owner"))) .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = translateMsg("poll.deadline"))) - layout.add(fieldset) + layout.add(fieldSet) layout.add( MenuItem( @@ -192,7 +219,9 @@ class ResponsePageRest : AbstractDynamicPageRest() { layout.add( UIButton.createDefaultButton( - id = "addResponse", title = translateMsg("poll.respond"), responseAction = ResponseAction( + id = "addResponse", + title = translateMsg("poll.respond"), + responseAction = ResponseAction( RestResolver.getRestUrl( this::class.java, "addResponse" @@ -200,15 +229,17 @@ class ResponsePageRest : AbstractDynamicPageRest() { ) ) ) - + layout.watchFields.add("delegationUser") LayoutUtils.process(layout) return FormLayoutData(pollResponse, layout, createServerData(request)) } @PostMapping("addResponse") fun addResponse( - request: HttpServletRequest, @RequestBody postData: PostData, @RequestParam("questionOwner") questionOwner: Int? + request: HttpServletRequest, + @RequestBody postData: PostData, @RequestParam("questionOwner") questionOwner: Int? ): ResponseEntity? { + val questionOwner: Int? = questionOwner val pollResponseDO = PollResponseDO() postData.data.copyTo(pollResponseDO) @@ -235,7 +266,27 @@ class ResponsePageRest : AbstractDynamicPageRest() { ) ) } + @PutMapping("showDelegatedUser") + fun showDelegatedUser( + request: HttpServletRequest, + @RequestBody postData: PostData + ): ResponseEntity? { + return ResponseEntity.ok( + ResponseAction( + PagesResolver.getDynamicPageUrl( + ResponsePageRest::class.java, + absolute = true + ) + "?number=${pollId}&delegatedUser=${delegatedUser?.id}", + targetType = TargetType.REDIRECT + ) + ) + } + @PostMapping(RestPaths.WATCH_FIELDS) + fun watchFields(@Valid @RequestBody postData: PostData): ResponseEntity { + delegatedUser = postData.data.delegationUser + return ResponseEntity.ok(ResponseAction(targetType = TargetType.UPDATE)) + } private fun transformPollFromDB(obj: PollDO): Poll { val poll = Poll() @@ -246,4 +297,13 @@ class ResponsePageRest : AbstractDynamicPageRest() { } return poll } + + fun hasFullAccess(dto: Poll): Boolean { + val loggedInUser = ThreadLocalUserContext.user + val foundUser = dto.fullAccessUsers?.any { user -> user.id == loggedInUser?.id } + if (foundUser == true || dto.owner?.id == loggedInUser?.id) { + return true + } + return false + } } \ No newline at end of file From e31339182147ae7c0c472f435617382152812052 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Thu, 1 Jun 2023 11:29:26 +0200 Subject: [PATCH 113/160] fix redirect --- .../rest/poll/ResponsePageRest.kt | 66 ++++++++++--------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index c787d2b3f3..3004f42988 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -45,11 +45,13 @@ class ResponsePageRest : AbstractDynamicPageRest() { var pollId: Int? = null var delegatedUser: User? = null + @GetMapping("dynamic") - fun getForm(request: HttpServletRequest, - @RequestParam("number") pollStringId: String?, - @RequestParam("delegatedUser") delUser: String?, - ): FormLayoutData { + fun getForm( + request: HttpServletRequest, + @RequestParam("number") pollStringId: String?, + @RequestParam("delegatedUser") delUser: String?, + ): FormLayoutData { pollId = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") //used to load awnsers, is an attendee chosen by a fullaccessuser in order to awnser for them or the Threadlocal User @@ -79,19 +81,20 @@ class ResponsePageRest : AbstractDynamicPageRest() { dataType = UIDataType.USER ) ) - .add( - UIButton.createDefaultButton( - id = "response-poll-button", - responseAction = ResponseAction( - RestResolver.getRestUrl( - this::class.java, - "showDelegatedUser" - ), targetType = TargetType.PUT - ), - title = "poll.response.page" - ), - )} - fieldSet + .add( + UIButton.createDefaultButton( + id = "response-poll-button", + responseAction = ResponseAction( + RestResolver.getRestUrl( + this::class.java, + "showDelegatedUser" + ), targetType = TargetType.PUT + ), + title = "poll.response.page" + ), + ) + } + fieldSet .add(UIReadOnlyField(value = pollDto.description, label = "Description")) .add(UIReadOnlyField(value = pollDto.location, label = "Location")) .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = "Owner")) @@ -103,7 +106,10 @@ class ResponsePageRest : AbstractDynamicPageRest() { fieldSet.add( UIButton.createExportButton( id = "export-poll-response-button", - responseAction = ResponseAction("${Rest.URL}/poll/export/${pollDto.id}", targetType = TargetType.POST), + responseAction = ResponseAction( + "${Rest.URL}/poll/export/${pollDto.id}", + targetType = TargetType.POST + ), title = "poll.export.response.poll" ) ) @@ -148,7 +154,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { pollResponse.poll = pollData pollResponseDao.internalLoadAll().firstOrNull { response -> - response.owner?.id == questionOwner + response.owner?.id == questionOwner && response.poll?.id == pollData.id }?.let { pollResponse.copyFrom(it) @@ -252,7 +258,8 @@ class ResponsePageRest : AbstractDynamicPageRest() { pollResponseDao.update(it) return ResponseEntity.ok( ResponseAction( - targetType = TargetType.REDIRECT, url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + targetType = TargetType.REDIRECT, + url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) ) ) } @@ -262,25 +269,24 @@ class ResponsePageRest : AbstractDynamicPageRest() { return ResponseEntity.ok( ResponseAction( - targetType = TargetType.REDIRECT, url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + targetType = TargetType.REDIRECT, + url = PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) ) ) } + @PutMapping("showDelegatedUser") fun showDelegatedUser( request: HttpServletRequest, @RequestBody postData: PostData ): ResponseEntity? { - return ResponseEntity.ok( - ResponseAction( - PagesResolver.getDynamicPageUrl( - ResponsePageRest::class.java, - absolute = true - ) + "?number=${pollId}&delegatedUser=${delegatedUser?.id}", - targetType = TargetType.REDIRECT - ) + return ResponseEntity.ok( + ResponseAction( + url = "/react/response/dynamic/?number=${pollId}&delegatedUser=${delegatedUser?.id}", + targetType = TargetType.REDIRECT ) - } + ) + } @PostMapping(RestPaths.WATCH_FIELDS) fun watchFields(@Valid @RequestBody postData: PostData): ResponseEntity { From 5752ef33b35fb53e9e56011f4ad7af6c038660e5 Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Fri, 2 Jun 2023 09:16:38 +0200 Subject: [PATCH 114/160] Add Access and Attendee Filter to overview Page --- .../org/projectforge/business/poll/PollDO.kt | 4 +- .../business/poll/filter/PollAssignment.kt | 2 +- .../poll/filter/PollAssignmentFilter.kt | 37 ++++++++++++++++++- .../main/resources/I18nResources.properties | 6 +++ .../resources/I18nResources_de.properties | 12 ++++-- .../projectforge/rest/poll/PollPageRest.kt | 10 ++--- 6 files changed, 58 insertions(+), 13 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 5b97c92fac..456429429a 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -76,13 +76,13 @@ open class PollDO : DefaultBaseDO() { if (currentUserId == this.owner?.id) { assignmentList.add(PollAssignment.OWNER) } - if (this.fullAccessUserIds != null) { + if (!this.fullAccessUserIds.isNullOrBlank()) { val accessUserIds = this.fullAccessUserIds!!.split(", ").map { it.toInt() }.toIntArray() if (accessUserIds.contains(currentUserId)) { assignmentList.add(PollAssignment.ACCESS) } } - if (this.attendeesIds != null) { + if (!this.attendeesIds.isNullOrBlank()) { val attendeeUserIds = this.attendeesIds!!.split(", ").map { it.toInt() }.toIntArray() if (attendeeUserIds.contains(currentUserId)) { assignmentList.add(PollAssignment.ATTENDEE) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignment.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignment.kt index 41b3b69611..7b76c8bd6f 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignment.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignment.kt @@ -6,5 +6,5 @@ enum class PollAssignment(val key: String) : I18nEnum { OWNER("owner"), ACCESS("access"), ATTENDEE("attendee"), OTHER("other"); override val i18nKey: String - get() = ("pollAssignment.$key") + get() = ("poll.$key") } \ No newline at end of file diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignmentFilter.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignmentFilter.kt index c452b2d8b9..b3d3e93b69 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignmentFilter.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignmentFilter.kt @@ -1,17 +1,50 @@ package org.projectforge.business.poll.filter +import org.projectforge.business.group.service.GroupService import org.projectforge.business.poll.PollDO +import org.projectforge.framework.configuration.ApplicationContextProvider import org.projectforge.framework.persistence.api.impl.CustomResultFilter +import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext +import org.projectforge.framework.persistence.user.entities.PFUserDO class PollAssignmentFilter(val values: List) : CustomResultFilter { override fun match(list: MutableList, element: PollDO): Boolean { + var foundUser: PFUserDO? = null + if (!element.fullAccessGroupIds.isNullOrEmpty()) { + val groupIds = element.fullAccessGroupIds!!.split(", ").map { it.toInt() }.toIntArray() + val accessUsers = groupService.getGroupUsers(groupIds) + val localUser = ThreadLocalUserContext.userId!! + foundUser = accessUsers.firstOrNull { user -> user.id == localUser } + } - element.getPollAssignment().forEach { pollAssignment -> - if (values.contains(pollAssignment)) { + values.forEach { filter -> + if (element.getPollAssignment() + .contains(filter) || (filter == PollAssignment.ACCESS && foundUser != null) + ) { return true } } return false + + /*element.getPollAssignment().forEach { pollAssignment -> + if (values.contains(pollAssignment)) { + return true + } + } + return false*/ + } + + companion object { + private var _groupService: GroupService? = null + private val groupService: GroupService + get() { + if (_groupService == null) { + _groupService = ApplicationContextProvider.getApplicationContext().getBean(GroupService::class.java) + } + return _groupService!! + } } + + } \ No newline at end of file diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index e9cfd90bf1..6b7efa3144 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -1983,6 +1983,12 @@ poll.date=Date poll.deadline=Deadline poll.location=Location poll.state=State +poll.assignment=Assignment +poll.access=Access +poll.attendee=Attendee +poll.other=Other +poll.running=Running +poll.finished=Finished poll.infopage=Info Page poll.guide=Poll Guide poll.question=Question diff --git a/projectforge-business/src/main/resources/I18nResources_de.properties b/projectforge-business/src/main/resources/I18nResources_de.properties index 87896648cc..7d6d3a4c4f 100644 --- a/projectforge-business/src/main/resources/I18nResources_de.properties +++ b/projectforge-business/src/main/resources/I18nResources_de.properties @@ -2062,16 +2062,22 @@ poll.title=Titel poll.title.list=Umfragen poll.title.add=Neue Umfrage erstellen poll.title.edit=Umfrage bearbeiten -poll.owner=Eigentümer +poll.owner=Eigentümer:in poll.description=Beschreibung -poll.attendees=Teilnehmer +poll.attendees=Teilnehmer:innen poll.groupAttendees=Teilnehmergruppen -poll.fullAccessUsers=Benutzer mit Vollzugriff +poll.fullAccessUsers=Benutzer:innen mit Vollzugriff poll.fullAccessGroups=Gruppen mit Vollzugriff poll.date=Datum poll.deadline=Antwortfrist poll.location=Ort poll.state=Status +poll.assignment=Zuordnung +poll.access=Zugriff +poll.attendee=Teilnehmer:in +poll.other=Andere +poll.running=Aktiv +poll.finished=Beendet poll.infopage=Infoseite poll.guide=Anleitung poll.question=Frage diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index c7b49e18ce..4e94801442 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -105,8 +105,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun addMagicFilterElements(elements: MutableList) { elements.add( - UIFilterListElement("assignment", label = translate("poll.pollAssignment"), defaultFilter = true) - .buildValues(PollAssignment.OWNER, PollAssignment.OTHER) + UIFilterListElement("assignment", label = translate("poll.assignment"), defaultFilter = true) + .buildValues(PollAssignment.OWNER, PollAssignment.ACCESS, PollAssignment.ATTENDEE, PollAssignment.OTHER) ) elements.add( UIFilterListElement("status", label = translate("poll.state"), defaultFilter = true) @@ -114,7 +114,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) } - override fun preProcessMagicFilter(target: QueryFilter, source: MagicFilter): List>? { + override fun preProcessMagicFilter(target: QueryFilter, source: MagicFilter): List> { val filters = mutableListOf>() val assignmentFilterEntry = source.entries.find { it.field == "assignment" } if (assignmentFilterEntry != null) { @@ -590,7 +590,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val pollDO = PollDO() pollDto.copyTo(pollDO) - return if (pollDao.hasFullAccess(pollDO) == false) { + return if (!pollDao.hasFullAccess(pollDO)) { // no full access user UILayout.UserAccess(insert = false, update = false, delete = false, history = false) } else { @@ -599,7 +599,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. UILayout.UserAccess(insert = true, update = true, delete = false, history = true) } else { // full access when viewing old poll - UILayout.UserAccess(insert = false, update = false, delete = true, history = false) + UILayout.UserAccess(insert = true, update = true, delete = true, history = false) } } } From 326ed2a308fe52276f09831e6e41e8b82863bc22 Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Fri, 2 Jun 2023 09:34:14 +0200 Subject: [PATCH 115/160] clean up code --- .../main/kotlin/org/projectforge/business/poll/PollDO.kt | 1 - .../business/poll/filter/PollAssignmentFilter.kt | 9 --------- 2 files changed, 10 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 456429429a..dfc530ba7e 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -12,7 +12,6 @@ import org.springframework.context.annotation.DependsOn import java.time.LocalDate import javax.persistence.* - @Entity @Indexed @Table(name = "t_poll") diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignmentFilter.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignmentFilter.kt index b3d3e93b69..f4c15a2dab 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignmentFilter.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignmentFilter.kt @@ -26,13 +26,6 @@ class PollAssignmentFilter(val values: List) : CustomResultFilte } } return false - - /*element.getPollAssignment().forEach { pollAssignment -> - if (values.contains(pollAssignment)) { - return true - } - } - return false*/ } companion object { @@ -45,6 +38,4 @@ class PollAssignmentFilter(val values: List) : CustomResultFilte return _groupService!! } } - - } \ No newline at end of file From 743f23d84717f422452a0045207c322faf4575ec Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Fri, 2 Jun 2023 15:46:12 +0200 Subject: [PATCH 116/160] Frontend changes. --- .../main/resources/I18nResources.properties | 1 + .../resources/I18nResources_de.properties | 7 +- .../projectforge/rest/poll/PollPageRest.kt | 46 +++++------ .../rest/poll/ResponsePageRest.kt | 76 +++++-------------- 4 files changed, 44 insertions(+), 86 deletions(-) diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index e9cfd90bf1..458fa19fa9 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -2004,6 +2004,7 @@ poll.export.response.poll=Export results poll.respond=Send responses poll.response.page=Poll Response Page poll.response.title=Poll Response Page +poll.userDelegation=User delegation poll.mail.endingSoon.subject=Poll ending in {0} days poll.mail.endingSoon.content=

Dear Attendees,

\

This is a friendly reminder that the poll "{0}" created by {1} is ending soon, on {2}. Please make sure to submit your responses before the deadline.

\ diff --git a/projectforge-business/src/main/resources/I18nResources_de.properties b/projectforge-business/src/main/resources/I18nResources_de.properties index b8d5c6cae0..7ee45bad1e 100644 --- a/projectforge-business/src/main/resources/I18nResources_de.properties +++ b/projectforge-business/src/main/resources/I18nResources_de.properties @@ -2063,20 +2063,14 @@ poll.title.list=Umfragen poll.title.add=Neue Umfrage erstellen poll.title.edit=Umfrage bearbeiten poll.owner=Eigentümer -poll.description=Beschreibung poll.attendees=Teilnehmer -poll.groupAttendees=Teilnehmergruppen poll.fullAccessUsers=Benutzer mit Vollzugriff -poll.fullAccessGroups=Gruppen mit Vollzugriff poll.date=Datum poll.deadline=Antwortfrist poll.location=Ort poll.state=Status -poll.delegationUser=Für anderen Nutzer abstimmen poll.description=Beschreibung -poll.error.oneQuestionRequired=Mindestens eine Frage ist erforderlich. poll.fullAccessGroups=Gruppen mit Vollzugriff -poll.fullAccessUsers=Benutzer mit Vollzugriff poll.groupAttendees=Teilnehmergruppen poll.infopage=Infoseite poll.guide=Anleitung @@ -2100,6 +2094,7 @@ poll.popup.closed=Umfrage wurde bereits beendet poll.respond=Antworten abschicken poll.response.page=Seite zur Umfrageantwort poll.response.title=Seite zur Umfrageantwort +poll.userDelegation=Für andere Nutzer abstimmen poll.mail.endingSoon.subject=Umfrage endet in {0} Tagen poll.mail.endingSoon.content=

Liebe Teilnehmerinnen und Teilnehmer,

\

wir möchten Sie daran erinnern, dass die Umfrage "{0}", erstellt von {1}, bald endet, nämlich am {2}. Bitte achten Sie darauf, Ihre Antworten vor dem Ablaufdatum einzureichen.

\ diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 4a26fb85cb..a75f62a2fd 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -9,6 +9,8 @@ import org.projectforge.framework.access.AccessException import org.projectforge.framework.i18n.translateMsg import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext +import org.projectforge.menu.MenuItem +import org.projectforge.menu.MenuItemTargetType import org.projectforge.rest.config.Rest import org.projectforge.rest.config.RestUtils import org.projectforge.rest.core.* @@ -116,35 +118,29 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ), ) ) - fieldset.add( - UIButton.createExportButton( - id = "export-poll-response-button", - responseAction = ResponseAction("${Rest.URL}/poll/export/${dto.id}", targetType = TargetType.POST), - title = "poll.export.response.poll" + layout.add( + MenuItem( + "export-poll-response-button", + i18nKey = "poll.export.response.poll", + url = RestResolver.getRestUrl( + restClass = PollPageRest::class.java, + subPath = "export", + params = mapOf("id" to dto.id) + ), + type = MenuItemTargetType.DOWNLOAD ) ) } - fieldset.add( - UIRow().add( - UICol( - UILength(10) - ) - ).add( - UICol( - UILength(1) - ).add( - UIButton.createLinkButton( - id = "poll-guide", title = "poll.guide", responseAction = ResponseAction( - PagesResolver.getDynamicPageUrl( - PollInfoPageRest::class.java, absolute = true - ), targetType = TargetType.MODAL - ) - ) - ) + + layout.add( + MenuItem( + "poll-guide", + i18nKey = "poll.guide", + url = PagesResolver.getDynamicPageUrl(PollInfoPageRest::class.java, absolute = false), + type = MenuItemTargetType.MODAL ) ) - addDefaultParameterFields(dto, fieldset, isRunning = dto.state == PollDO.State.RUNNING) fieldset @@ -525,8 +521,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } - @PostMapping("/export/{id}") - fun export(@PathVariable("id") id: String): ResponseEntity? { + @GetMapping("export") + fun export(@RequestParam("id") id: String): ResponseEntity? { val poll = Poll() val pollDo = pollDao.getById(id.toInt()) poll.copyFrom(pollDo) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 3004f42988..e2d6a0c3bc 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -43,8 +43,8 @@ class ResponsePageRest : AbstractDynamicPageRest() { @Autowired private lateinit var userService: UserService - var pollId: Int? = null - var delegatedUser: User? = null + private var pollId: Int? = null + private var delegatedUser: User? = null @GetMapping("dynamic") fun getForm( @@ -54,15 +54,14 @@ class ResponsePageRest : AbstractDynamicPageRest() { ): FormLayoutData { pollId = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") - //used to load awnsers, is an attendee chosen by a fullaccessuser in order to awnser for them or the Threadlocal User - var questionOwner: Int? = null - + //used to load answers, is an attendee chosen by a fullAccessUser in order to answer for them or the ThreadLocal User val pollData = pollDao.internalGetById(pollId) ?: PollDO() - if (delUser != null && pollDao.hasFullAccess(pollData) && pollDao.isAttendee(pollData, delUser.toInt())) - questionOwner = delUser.toInt() - else - questionOwner = ThreadLocalUserContext.user?.id + val questionOwner = + if (delUser != null && pollDao.hasFullAccess(pollData) && pollDao.isAttendee(pollData, delUser.toInt())) + delUser.toInt() + else + ThreadLocalUserContext.user?.id val questionOwnerName = userService.getUser(questionOwner).displayName val pollDto = transformPollFromDB(pollData) @@ -72,12 +71,14 @@ class ResponsePageRest : AbstractDynamicPageRest() { } val layout = UILayout("poll.response.title") - val fieldSet = UIFieldset(12, title = pollDto.title + " Antworten von " + questionOwnerName) + + val fieldSetDelegationUser = UIFieldset(title = "poll.userDelegation") + if (hasFullAccess(pollDto)) { - fieldSet.add( + fieldSetDelegationUser.add( UIInput( id = "delegationUser", - label = "poll.delegationUser", + label = "user", dataType = UIDataType.USER ) ) @@ -94,46 +95,15 @@ class ResponsePageRest : AbstractDynamicPageRest() { ), ) } + layout.add(fieldSetDelegationUser) + + val fieldSet = UIFieldset(12, title = pollDto.title + " Antworten von " + questionOwnerName) fieldSet .add(UIReadOnlyField(value = pollDto.description, label = "Description")) .add(UIReadOnlyField(value = pollDto.location, label = "Location")) .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = "Owner")) .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = "Deadline")) - - - if (pollDto.isFinished() == false && pollDto.isAlreadyCreated() && pollDao.hasFullAccess(pollData)) { - fieldSet.add( - UIButton.createExportButton( - id = "export-poll-response-button", - responseAction = ResponseAction( - "${Rest.URL}/poll/export/${pollDto.id}", - targetType = TargetType.POST - ), - title = "poll.export.response.poll" - ) - ) - } - fieldSet.add( - UIRow().add( - UICol( - UILength(10) - ) - ).add( - UICol( - UILength(1) - ).add( - UIButton.createLinkButton( - id = "poll-guide", title = "poll.guide", responseAction = ResponseAction( - PagesResolver.getDynamicPageUrl( - PollInfoPageRest::class.java, absolute = true - ), targetType = TargetType.MODAL - ) - ) - ) - ) - ) - fieldSet.add(UIReadOnlyField(value = pollDto.description, label = translateMsg("poll.description"))) .add(UIReadOnlyField(value = pollDto.location, label = translateMsg("poll.location"))) .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = translateMsg("poll.owner"))) @@ -161,7 +131,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { } pollDto.inputFields?.forEachIndexed { index, field -> - val fieldSet2 = UIFieldset(title = field.question) + val fieldSetQuestions = UIFieldset(title = field.question) val questionAnswer = QuestionAnswer() questionAnswer.uid = UUID.randomUUID().toString() questionAnswer.questionUid = field.uid @@ -219,8 +189,8 @@ class ResponsePageRest : AbstractDynamicPageRest() { col.add(UICheckbox("responses[$index].answers[$index2]", label = field.answers?.get(index2) ?: "")) } } - fieldSet2.add(UIRow().add(col)) - layout.add(fieldSet2) + fieldSetQuestions.add(UIRow().add(col)) + layout.add(fieldSetQuestions) } layout.add( @@ -245,7 +215,6 @@ class ResponsePageRest : AbstractDynamicPageRest() { request: HttpServletRequest, @RequestBody postData: PostData, @RequestParam("questionOwner") questionOwner: Int? ): ResponseEntity? { - val questionOwner: Int? = questionOwner val pollResponseDO = PollResponseDO() postData.data.copyTo(pollResponseDO) @@ -304,12 +273,9 @@ class ResponsePageRest : AbstractDynamicPageRest() { return poll } - fun hasFullAccess(dto: Poll): Boolean { + private fun hasFullAccess(dto: Poll): Boolean { val loggedInUser = ThreadLocalUserContext.user val foundUser = dto.fullAccessUsers?.any { user -> user.id == loggedInUser?.id } - if (foundUser == true || dto.owner?.id == loggedInUser?.id) { - return true - } - return false + return foundUser == true || dto.owner?.id == loggedInUser?.id } } \ No newline at end of file From 64063faf04406b061247ed22d817b67d0a12cfa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Mon, 5 Jun 2023 11:27:40 +0200 Subject: [PATCH 117/160] Fixed PollAssignment check and info popup text --- .../src/main/resources/I18nResources.properties | 7 +++---- .../src/main/resources/I18nResources_de.properties | 5 ++--- .../org/projectforge/rest/poll/PollPageRest.kt | 8 +------- .../org/projectforge/rest/poll/ResponsePageRest.kt | 12 ++---------- 4 files changed, 8 insertions(+), 24 deletions(-) diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index e9cfd90bf1..5a1e402616 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -1992,10 +1992,9 @@ poll.answer=Answer poll.button.addQuestion=Add own question poll.button.micromataTemplate=Use Micromata Template poll.button.finish=Finish Poll -poll.confirmation.finish=Do you really want to Finish this Poll? -poll.confirmation.creation=Do you really want to create the poll? You won't be able to edit the questions afterwards. -poll.confirmation.creationNoAttendees=Do you really want to create the poll? You won't be able to edit the questions afterwards.\ -You also have not added any attendees yet. Be sure to add attendees for your poll. +poll.confirmation.finish=Do you really want to finish this Poll? +poll.confirmation.creation=Do you really want to create the poll? You won't be able to edit the questions afterwards.\ +Also make sure to add attendees for your poll. poll.confirmation.deleteAnswer=Do you really want to delete this answer? poll.confirmation.deleteQuestion=Do you really want to delete this question? poll.popup.closed=The poll was already closed. diff --git a/projectforge-business/src/main/resources/I18nResources_de.properties b/projectforge-business/src/main/resources/I18nResources_de.properties index 87896648cc..94f97ef262 100644 --- a/projectforge-business/src/main/resources/I18nResources_de.properties +++ b/projectforge-business/src/main/resources/I18nResources_de.properties @@ -2082,9 +2082,8 @@ poll.button.addQuestion=Eigene Frage hinzuf poll.button.micromataTemplate=Micromata Template verwenden poll.button.finish=Umfrage beenden poll.confirmation.finish=Willst du die Umfrage wirklich beenden? -poll.confirmation.creation=Möchtest du die Umfrage wirklich erstellen? Du kannst die Fragen danach nicht mehr bearbeiten. -poll.confirmation.creationNoAttendees=Möchten Sie die Umfrage wirklich erstellen? Sie können die Fragen anschließend nicht mehr bearbeiten.\ -Sie haben auch noch keine Teilnehmer hinzugefügt. Stellen Sie sicher, dass Sie Teilnehmer zu Ihrer Umfrage hinzufügen. +poll.confirmation.creation=Möchten Sie die Umfrage wirklich erstellen? Sie können die Fragen anschließend nicht mehr bearbeiten.\ +Stellen Sie sicher, dass Sie Teilnehmer zu Ihrer Umfrage hinzugefügt haben. poll.confirmation.deleteQuestion=Möchtest du diese Frage wirklich löschen? poll.confirmation.deleteAnswer=Möchtest du diese Antwort wirklich löschen? poll.error.closed=Umfrage wurde bereits beendet diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 32d3fc1c32..11420594ad 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -167,16 +167,10 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. layout.watchFields.addAll(listOf("groupAttendees")) - val confirmMessage = if (dto.attendees.isNullOrEmpty()) { - translateMsg("poll.confirmation.creationNoAttendees") - } else { - translateMsg("poll.confirmation.creation") - } - val processedLayout = LayoutUtils.processEditPage(layout, dto, this) processedLayout.actions.filterIsInstance().find { it.id == "create" - }?.confirmMessage = confirmMessage + }?.confirmMessage = translateMsg("poll.confirmation.creation") if (dto.isAlreadyCreated()) { diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index b5511d985b..cf25fba0b4 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -1,12 +1,8 @@ package org.projectforge.rest.poll import com.fasterxml.jackson.databind.ObjectMapper -import org.projectforge.business.poll.PollDO -import org.projectforge.business.poll.PollDao -import org.projectforge.business.poll.PollResponseDO -import org.projectforge.business.poll.PollResponseDao -import org.projectforge.framework.access.AccessException import org.projectforge.business.poll.* +import org.projectforge.framework.access.AccessException import org.projectforge.framework.i18n.translateMsg import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.utils.NumberHelper @@ -48,18 +44,14 @@ class ResponsePageRest : AbstractDynamicPageRest() { throw AccessException("access.exception.noAccess", "Umfrage nicht gefunden.") } - if (pollData.getPollAssignment().contains(PollAssignment.ATTENDEE)) { + if (!pollData.getPollAssignment().contains(PollAssignment.ATTENDEE)) { throw AccessException("access.exception.noAccess", "Du darfst nicht auf diese Umfrage antworten.") } - if (pollDto.state == PollDO.State.FINISHED) { - throw AccessException("access.exception.noAccess", "poll.error.closed") - } val layout = UILayout("poll.response.title") val fieldset = UIFieldset(12, title = pollDto.title) - if (pollDto.isFinished() == false && pollDto.isAlreadyCreated() && pollDao.hasFullAccess(pollData)) { fieldset.add( UIButton.createExportButton( From b6947fd13e257888e263b72290e537913274ca47 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Mon, 5 Jun 2023 11:37:14 +0200 Subject: [PATCH 118/160] change delUser Select is only there if you are on your response page --- .../projectforge/rest/poll/PollPageRest.kt | 2 +- .../rest/poll/ResponsePageRest.kt | 37 +++++++++---------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 4a26fb85cb..9a0f5c595f 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -82,7 +82,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. * @return the response page. */ override fun getStandardEditPage(): String { - return "${PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java)}?number=:id" + return "${PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java)}?pollId=:id" } override fun createListLayout( diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 3004f42988..cc1ede07a6 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -19,7 +19,6 @@ import org.projectforge.rest.core.PagesResolver import org.projectforge.rest.core.RestResolver import org.projectforge.rest.dto.FormLayoutData import org.projectforge.rest.dto.PostData -import org.projectforge.rest.dto.User import org.projectforge.rest.poll.types.* import org.projectforge.ui.* import org.springframework.beans.factory.annotation.Autowired @@ -44,27 +43,27 @@ class ResponsePageRest : AbstractDynamicPageRest() { private lateinit var userService: UserService var pollId: Int? = null - var delegatedUser: User? = null + var questionOwnerId: Int? = null @GetMapping("dynamic") fun getForm( request: HttpServletRequest, - @RequestParam("number") pollStringId: String?, - @RequestParam("delegatedUser") delUser: String?, + @RequestParam("pollId") number: String?, + @RequestParam("questionOwner") delUser: String?, ): FormLayoutData { - pollId = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") - + if (pollId === null || (number != null && pollId != null)) { + pollId = NumberHelper.parseInteger(number) ?: throw IllegalArgumentException("id not given.") + } //used to load awnsers, is an attendee chosen by a fullaccessuser in order to awnser for them or the Threadlocal User - var questionOwner: Int? = null val pollData = pollDao.internalGetById(pollId) ?: PollDO() if (delUser != null && pollDao.hasFullAccess(pollData) && pollDao.isAttendee(pollData, delUser.toInt())) - questionOwner = delUser.toInt() + questionOwnerId = delUser.toInt() else - questionOwner = ThreadLocalUserContext.user?.id + questionOwnerId = ThreadLocalUserContext.user?.id - val questionOwnerName = userService.getUser(questionOwner).displayName + val questionOwnerName = userService.getUser(questionOwnerId).displayName val pollDto = transformPollFromDB(pollData) if (pollDto.state == PollDO.State.FINISHED) { @@ -73,7 +72,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { val layout = UILayout("poll.response.title") val fieldSet = UIFieldset(12, title = pollDto.title + " Antworten von " + questionOwnerName) - if (hasFullAccess(pollDto)) { + if (hasFullAccess(pollDto) && ThreadLocalUserContext.user?.id === questionOwnerId) { fieldSet.add( UIInput( id = "delegationUser", @@ -88,7 +87,8 @@ class ResponsePageRest : AbstractDynamicPageRest() { RestResolver.getRestUrl( this::class.java, "showDelegatedUser" - ), targetType = TargetType.PUT + ), + targetType = TargetType.GET ), title = "poll.response.page" ), @@ -154,7 +154,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { pollResponse.poll = pollData pollResponseDao.internalLoadAll().firstOrNull { response -> - response.owner?.id == questionOwner + response.owner?.id == questionOwnerId && response.poll?.id == pollData.id }?.let { pollResponse.copyFrom(it) @@ -231,7 +231,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { RestResolver.getRestUrl( this::class.java, "addResponse" - ) + "/?questionOwner=${questionOwner}", targetType = TargetType.POST + ) + "/?questionOwner=${questionOwnerId}", targetType = TargetType.POST ) ) ) @@ -275,14 +275,13 @@ class ResponsePageRest : AbstractDynamicPageRest() { ) } - @PutMapping("showDelegatedUser") + @GetMapping("showDelegatedUser") fun showDelegatedUser( - request: HttpServletRequest, - @RequestBody postData: PostData + request: HttpServletRequest ): ResponseEntity? { return ResponseEntity.ok( ResponseAction( - url = "/react/response/dynamic/?number=${pollId}&delegatedUser=${delegatedUser?.id}", + url = "/react/response/dynamic/?pollId=${pollId}&questionOwner=${questionOwnerId}", targetType = TargetType.REDIRECT ) ) @@ -290,7 +289,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { @PostMapping(RestPaths.WATCH_FIELDS) fun watchFields(@Valid @RequestBody postData: PostData): ResponseEntity { - delegatedUser = postData.data.delegationUser + questionOwnerId = postData.data.delegationUser?.id return ResponseEntity.ok(ResponseAction(targetType = TargetType.UPDATE)) } From b1f87fe08a02bc1a702d4bbfafbf9f66402ab74b Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Mon, 5 Jun 2023 16:41:30 +0200 Subject: [PATCH 119/160] change delUser Select is only there if you are on your response page v2 --- .../projectforge/rest/poll/PollPageRest.kt | 2 +- .../rest/poll/ResponsePageRest.kt | 38 ++++++++++--------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index a1a4f504b4..15006504ec 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -111,7 +111,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. PagesResolver.getDynamicPageUrl( ResponsePageRest::class.java, absolute = true - ) + "?pollid=${dto.id}&questionOwner=${dto.delegationUser?.id}", + ) + "?pollId=${dto.id}&questionOwner=${dto.delegationUser?.id}", targetType = TargetType.GET ), title = "poll.response.page" diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 4067a0a99a..2cafefeac8 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -48,8 +48,8 @@ class ResponsePageRest : AbstractDynamicPageRest() { @GetMapping("dynamic") fun getForm( request: HttpServletRequest, - @RequestParam("number") pollStringId: String?, - @RequestParam("delegatedUser") delUser: String?, + @RequestParam("pollId") pollStringId: String?, + @RequestParam("questionOwner") delUser: String?, ): FormLayoutData { if (pollId === null || (pollStringId != null && pollId != null)) { pollId = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") @@ -57,10 +57,11 @@ class ResponsePageRest : AbstractDynamicPageRest() { //used to load answers, is an attendee chosen by a fullAccessUser in order to answer for them or the ThreadLocal User val pollData = pollDao.internalGetById(pollId) ?: PollDO() - questionOwnerId = if (delUser != null && pollDao.hasFullAccess(pollData) && pollDao.isAttendee(pollData, delUser.toInt())) - delUser.toInt() - else - ThreadLocalUserContext.user?.id + questionOwnerId = + if (delUser != null && pollDao.hasFullAccess(pollData) && pollDao.isAttendee(pollData, delUser.toInt())) + delUser.toInt() + else + ThreadLocalUserContext.user?.id val questionOwnerName = userService.getUser(questionOwnerId).displayName val pollDto = transformPollFromDB(pollData) @@ -71,9 +72,9 @@ class ResponsePageRest : AbstractDynamicPageRest() { val layout = UILayout("poll.response.title") - val fieldSetDelegationUser = UIFieldset(title = "poll.userDelegation") - if (hasFullAccess(pollDto)) { + if (hasFullAccess(pollDto) && ThreadLocalUserContext.user?.id === questionOwnerId) { + val fieldSetDelegationUser = UIFieldset(title = "poll.userDelegation") fieldSetDelegationUser.add( UIInput( id = "delegationUser", @@ -94,8 +95,19 @@ class ResponsePageRest : AbstractDynamicPageRest() { title = "poll.response.page" ), ) + layout.add(fieldSetDelegationUser) + } + + if (hasFullAccess(pollDto)) { + layout.add( + MenuItem( + "EDIT", + i18nKey = "poll.title.edit", + url = PagesResolver.getEditPageUrl(PollPageRest::class.java, pollDto.id), + type = MenuItemTargetType.REDIRECT + ) + ) } - layout.add(fieldSetDelegationUser) val fieldSet = UIFieldset(12, title = pollDto.title + " Antworten von " + questionOwnerName) fieldSet @@ -111,14 +123,6 @@ class ResponsePageRest : AbstractDynamicPageRest() { layout.add(fieldSet) - layout.add( - MenuItem( - "EDIT", - i18nKey = "poll.title.edit", - url = PagesResolver.getEditPageUrl(PollPageRest::class.java, pollDto.id), - type = MenuItemTargetType.REDIRECT - ) - ) val pollResponse = PollResponse() pollResponse.poll = pollData From 90548b5907212fea18644c53cb1bee92cbf5761c Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Tue, 6 Jun 2023 12:44:23 +0200 Subject: [PATCH 120/160] remove cronJobs --- .../src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/CronJobs.kt deleted file mode 100644 index e69de29bb2..0000000000 From 528a32664429b9e5dfdba2e5afc89c471e360c56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Mon, 19 Jun 2023 12:55:48 +0200 Subject: [PATCH 121/160] Frontend changes and smaller fixes --- .../main/resources/I18nResources.properties | 8 +- .../resources/I18nResources_de.properties | 8 +- .../rest/poll/PollInfoPageRest.kt | 21 +-- .../projectforge/rest/poll/PollPageRest.kt | 122 +++++++--------- .../rest/poll/ResponsePageRest.kt | 138 ++++++++++-------- 5 files changed, 142 insertions(+), 155 deletions(-) diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index 76dd0a6f47..591301c986 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -1995,13 +1995,14 @@ poll.question=Question poll.question.textQuestion=Text Question poll.questionType=Question Type poll.answer=Answer +poll.yourAnswers=Your answers +poll.delegationAnswers=Answers of poll.button.addQuestion=Add own question poll.button.micromataTemplate=Use Micromata Template poll.button.finish=Finish Poll poll.confirmation.finish=Do you really want to Finish this Poll? -poll.confirmation.creation=Do you really want to create the poll? You won't be able to edit the questions afterwards. -poll.confirmation.creationNoAttendees=Do you really want to create the poll? You won't be able to edit the questions afterwards.\ -You also have not added any attendees yet. Be sure to add attendees for your poll. +poll.confirmation.creation=Do you really want to create the poll? You won't be able to edit the questions afterwards.\ +Also make sure to add attendees for your poll. poll.confirmation.deleteAnswer=Do you really want to delete this answer? poll.confirmation.deleteQuestion=Do you really want to delete this question? poll.popup.closed=The poll was already closed. @@ -2011,6 +2012,7 @@ poll.respond=Send responses poll.response.page=Poll Response Page poll.response.title=Poll Response Page poll.userDelegation=User delegation +poll.selectUser=Select user poll.mail.endingSoon.subject=Poll ending in {0} days poll.mail.endingSoon.content=

Dear Attendees,

\

This is a friendly reminder that the poll "{0}" created by {1} is ending soon, on {2}. Please make sure to submit your responses before the deadline.

\ diff --git a/projectforge-business/src/main/resources/I18nResources_de.properties b/projectforge-business/src/main/resources/I18nResources_de.properties index 5911e1f495..2cbd0df3be 100644 --- a/projectforge-business/src/main/resources/I18nResources_de.properties +++ b/projectforge-business/src/main/resources/I18nResources_de.properties @@ -2084,13 +2084,14 @@ poll.question=Frage poll.question.textQuestion=Textfrage poll.questionType=Fragen Typ poll.answer=Antwort +poll.yourAnswers=Deine Antworten +poll.delegationAnswers=Antworten von poll.button.addQuestion=Eigene Frage hinzufügen poll.button.micromataTemplate=Micromata Template verwenden poll.button.finish=Umfrage beenden poll.confirmation.finish=Willst du die Umfrage wirklich beenden? -poll.confirmation.creation=Möchtest du die Umfrage wirklich erstellen? Du kannst die Fragen danach nicht mehr bearbeiten. -poll.confirmation.creationNoAttendees=Möchten Sie die Umfrage wirklich erstellen? Sie können die Fragen anschließend nicht mehr bearbeiten.\ -Sie haben auch noch keine Teilnehmer hinzugefügt. Stellen Sie sicher, dass Sie Teilnehmer zu Ihrer Umfrage hinzufügen. +poll.confirmation.creation=Möchtest du die Umfrage wirklich erstellen? Du kannst die Fragen danach nicht mehr bearbeiten.\ +Stelle ebenfalls sicher, dass du Teilnehmer für deine Umfrage hinzugefügt hast. poll.confirmation.deleteQuestion=Möchtest du diese Frage wirklich löschen? poll.confirmation.deleteAnswer=Möchtest du diese Antwort wirklich löschen? poll.error.closed=Umfrage wurde bereits beendet @@ -2101,6 +2102,7 @@ poll.respond=Antworten abschicken poll.response.page=Seite zur Umfrageantwort poll.response.title=Seite zur Umfrageantwort poll.userDelegation=Für andere Nutzer abstimmen +poll.selectUser=Nutzer auswählen poll.mail.endingSoon.subject=Umfrage endet in {0} Tagen poll.mail.endingSoon.content=

Liebe Teilnehmerinnen und Teilnehmer,

\

wir möchten Sie daran erinnern, dass die Umfrage "{0}", erstellt von {1}, bald endet, nämlich am {2}. Bitte achten Sie darauf, Ihre Antworten vor dem Ablaufdatum einzureichen.

\ diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt index eeadae2083..537967acb0 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt @@ -2,7 +2,6 @@ package org.projectforge.rest.poll import org.projectforge.rest.config.Rest import org.projectforge.rest.core.AbstractDynamicPageRest -import org.projectforge.rest.core.PagesResolver import org.projectforge.rest.dto.FormLayoutData import org.projectforge.ui.* import org.springframework.web.bind.annotation.GetMapping @@ -65,13 +64,13 @@ class PollInfoPageRest : AbstractDynamicPageRest() { layout.add( UIFieldset().add(UILabel("YesNoQuestion")).add( UICol() - .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage, die mit Ja oder Nein beantwortet werden kann")) + .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage, die mit Ja oder Nein beantwortet werden kann.")) ) ) layout.add( UIFieldset().add(UILabel("MultipleChoiceQuestion")).add( UICol() - .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage, die mit mehreren Antworten beantwortet werden kann")) + .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage, die mit mehreren Antworten beantwortet werden kann.")) ) ) layout.add( @@ -81,31 +80,19 @@ class PollInfoPageRest : AbstractDynamicPageRest() { UIReadOnlyField( "question", label = "Question", - value = "Eine Frage, die mit einer Freitext Antwort beantwortet werden kann" + value = "Eine Frage, die mit einer Freitext Antwort beantwortet werden kann." ) ) ) ) - layout.add( - UIFieldset().add(UILabel("DateQuestion")).add( - UICol() - .add( - UIReadOnlyField( - "question", label = "Question", - value = """Eine Frage, ob an einem Tag (Uhrzeit) die Teilnehmer Zeit haben. Die mit einem Ja, Nein oder Vielleicht - beantwortet werden kann""" - ) - ) - ) - ) layout.add( UIFieldset().add(UILabel("Dropdown")).add( UICol() .add( UIReadOnlyField( "question", label = "Question", - value = """ Eine Frage, die mit einem Dropdown beantwortet werden kann""" + value = "Eine Frage, die mit einem Dropdown beantwortet werden kann." ) ) ) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index dba7bfb3ea..faddaf8169 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -4,11 +4,11 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.projectforge.business.group.service.GroupService import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao +import org.projectforge.business.poll.PollResponseDao import org.projectforge.business.poll.filter.PollAssignment import org.projectforge.business.poll.filter.PollAssignmentFilter import org.projectforge.business.poll.filter.PollState import org.projectforge.business.poll.filter.PollStateFilter -import org.projectforge.business.poll.PollResponseDao import org.projectforge.business.user.service.UserService import org.projectforge.framework.access.AccessException import org.projectforge.framework.i18n.translate @@ -156,23 +156,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun createEditLayout(dto: Poll, userAccess: UILayout.UserAccess): UILayout { val layout = super.createEditLayout(dto, userAccess) val fieldset = UIFieldset(UILength(12)) + layout.add(fieldset) if (dto.state == PollDO.State.RUNNING && dto.isAlreadyCreated()) { - fieldset.add( - UIRow() - .add( - UIButton.createDefaultButton( - id = "response-poll-button", - responseAction = ResponseAction( - PagesResolver.getDynamicPageUrl( - ResponsePageRest::class.java, - absolute = true - ) + "?pollId=${dto.id}&questionOwner=${dto.delegationUser?.id}", - targetType = TargetType.GET - ), - title = "poll.response.page" - ), - ) - ) layout.add( MenuItem( "export-poll-response-button", @@ -219,6 +204,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) .add( UICol(UILength(xs = 3, sm = 3, md = 3, lg = 3)) + .add(UISpacer()) .add( UIButton.createDefaultButton( id = "add-question-button", @@ -253,39 +239,33 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) ) } - layout.add(fieldset) - - addQuestionFieldset(layout, dto) + addQuestionFieldset(layout, dto, fieldset) layout.watchFields.add("delegationUser") layout.watchFields.addAll(listOf("groupAttendees")) - val confirmMessage = if (dto.attendees.isNullOrEmpty()) { - translateMsg("poll.confirmation.creationNoAttendees") - } else { - translateMsg("poll.confirmation.creation") - } - val processedLayout = LayoutUtils.processEditPage(layout, dto, this) - processedLayout.actions.filterIsInstance().find { - it.id == "create" - }?.confirmMessage = confirmMessage - - - if (dto.isAlreadyCreated()) { - processedLayout.addAction( - UIButton.createDangerButton( - id = "poll-button-finish", - title = "poll.button.finish", - confirmMessage = translateMsg("poll.confirmation.finish"), - responseAction = ResponseAction( - "${Rest.URL}/poll/finish", - targetType = TargetType.PUT - ), - layout = layout, + if (!dto.isFinished()) { + processedLayout.actions.filterIsInstance().find { + it.id == "create" + }?.confirmMessage = translateMsg("poll.confirmation.creation") + + + if (dto.isAlreadyCreated()) { + processedLayout.addAction( + UIButton.createDangerButton( + id = "poll-button-finish", + title = "poll.button.finish", + confirmMessage = translateMsg("poll.confirmation.finish"), + responseAction = ResponseAction( + "${Rest.URL}/poll/finish", + targetType = TargetType.PUT + ), + layout = layout, + ) ) - ) + } } return processedLayout @@ -293,7 +273,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @PutMapping("/finish") - fun changeStateToFinsish( + fun changeStateToFinish( request: HttpServletRequest, @RequestBody postData: PostData ): ResponseEntity { @@ -303,6 +283,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return super.saveOrUpdate(request, postData) } + override fun onBeforeSaveOrUpdate(request: HttpServletRequest, obj: PollDO, postData: PostData) { if (obj.inputFields.isNullOrEmpty() || obj.inputFields.equals("[]")) { throw AccessException("poll.error.oneQuestionRequired") @@ -344,7 +325,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @PathVariable("fieldId") fieldUid: String, ): ResponseEntity { val dto = postData.data - val userAccess = getUserAccess(dto) val found = dto.inputFields?.find { it.uid == fieldUid } found?.answers?.add("") @@ -352,7 +332,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto).addVariable( "ui", - createEditLayout(dto, userAccess) + createEditLayout(dto, getUserAccess(dto)) ) ) } @@ -363,7 +343,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @RequestBody postData: PostData ): ResponseEntity { val dto = postData.data - val userAccess = getUserAccess(dto) val type = dto.questionType?.let { BaseType.valueOf(it) } ?: BaseType.TextQuestion val question = Question(uid = UUID.randomUUID().toString(), type = type) @@ -375,7 +354,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.owner = userService.getUser(dto.owner?.id) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto) - .addVariable("ui", createEditLayout(dto, userAccess)) + .addVariable("ui", createEditLayout(dto, getUserAccess(dto))) ) } @@ -404,7 +383,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dto.attendees = allUsers.sortedBy { it.displayName } } dto.owner = userService.getUser(dto.owner?.id) - val userAccess = getUserAccess(dto) // I dont know why this is necessary if (watchFieldsTriggered?.get(0) == "delegationUser") { @@ -415,7 +393,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ResponseAction( targetType = TargetType.UPDATE ) - .addVariable("ui", createEditLayout(dto, userAccess)) + .addVariable("ui", createEditLayout(dto, getUserAccess(dto))) .addVariable("data", dto) ) } @@ -426,7 +404,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @RequestBody postData: PostData, ): ResponseEntity { val dto = postData.data - val userAccess = getUserAccess(dto) PREMADE_QUESTIONS.entries.forEach { entry -> dto.inputFields?.add(entry.value) @@ -434,18 +411,19 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto) - .addVariable("ui", createEditLayout(dto, userAccess)) + .addVariable("ui", createEditLayout(dto, getUserAccess(dto))) ) } - private fun addQuestionFieldset(layout: UILayout, dto: Poll) { + private fun addQuestionFieldset(layout: UILayout, dto: Poll, fieldset: UIFieldset) { + fieldset.add(UISpacer()) dto.inputFields?.forEachIndexed { index, field -> - val fieldset = UIFieldset(UILength(12), title = field.type.toString()) + val questionFieldset = UIFieldset(UILength(12), title = field.type.toString()) if (!dto.isAlreadyCreated()) { - fieldset.add(generateDeleteButton(layout, field.uid)) + questionFieldset.add(generateDeleteButton(layout, field.uid)) } - fieldset.add( + questionFieldset.add( getUiElement( dto.isAlreadyCreated(), "inputFields[${index}].question", @@ -455,7 +433,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. if (field.type == BaseType.SingleResponseQuestion || field.type == BaseType.MultiResponseQuestion) { field.answers?.forEachIndexed { answerIndex, _ -> - fieldset.add( + questionFieldset.add( generateSingleAndMultiResponseAnswer( dto.isAlreadyCreated(), index, @@ -464,22 +442,24 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. layout ) ) + .add(UISpacer()) } if (!dto.isAlreadyCreated()) { - fieldset.add( - UIRow().add( - UIButton.createAddButton( - responseAction = ResponseAction( - "${Rest.URL}/poll/addAnswer/${field.uid}", targetType = TargetType.POST - ), - default = false + questionFieldset.add( + UIRow() + .add( + UIButton.createAddButton( + responseAction = ResponseAction( + "${Rest.URL}/poll/addAnswer/${field.uid}", targetType = TargetType.POST + ), + default = false + ) ) - ) ) } } - layout.add(fieldset) + fieldset.add(questionFieldset) } } @@ -505,6 +485,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. if (!objGiven) { row.add( UICol() + .add(UISpacer()) .add( UIButton.createDangerButton( id = "X", @@ -528,13 +509,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. @PathVariable("answerIndex") answerIndex: Int ): ResponseEntity { val dto = postData.data - val userAccess = getUserAccess(dto) dto.inputFields?.find { it.uid.equals(questionUid) }?.answers?.removeAt(answerIndex) dto.owner = userService.getUser(dto.owner?.id) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto) - .addVariable("ui", createEditLayout(dto, userAccess)) + .addVariable("ui", createEditLayout(dto, getUserAccess(dto))) ) } @@ -655,10 +635,14 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. // no full access user UILayout.UserAccess(insert = false, update = false, delete = false, history = false) } else { - if (pollDto.id == null) { + if (!pollDto.isAlreadyCreated()) { // full access when creating new poll UILayout.UserAccess(insert = true, update = true, delete = false, history = true) } else { + if (pollDto.isFinished()) { + // full access when viewing finished poll + UILayout.UserAccess(insert = false, update = false, delete = true, history = false) + } // full access when viewing old poll UILayout.UserAccess(insert = true, update = true, delete = true, history = false) } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 526abad6b9..d957243547 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -6,7 +6,6 @@ import org.projectforge.business.poll.PollDao import org.projectforge.business.poll.PollResponseDO import org.projectforge.business.poll.PollResponseDao import org.projectforge.business.user.service.UserService -import org.projectforge.framework.access.AccessException import org.projectforge.framework.i18n.translateMsg import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.utils.NumberHelper @@ -50,57 +49,31 @@ class ResponsePageRest : AbstractDynamicPageRest() { request: HttpServletRequest, @RequestParam("pollId") pollStringId: String?, @RequestParam("questionOwner") delUser: String?, + @RequestParam("returnToCaller") returnToCaller: String?, ): FormLayoutData { if (pollId === null || (pollStringId != null && pollId != null)) { pollId = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") } - //used to load answers, is an attendee chosen by a fullAccessUser in order to answer for them or the ThreadLocal User + // used to load answers, is an attendee chosen by a fullAccessUser in order to answer for them or the ThreadLocal User val pollData = pollDao.internalGetById(pollId) ?: PollDO() - questionOwnerId = - if (delUser != null && pollDao.hasFullAccess(pollData) && pollDao.isAttendee(pollData, delUser.toInt())) - delUser.toInt() - else - ThreadLocalUserContext.user?.id + var answerTitle = "" + if (delUser != null && pollDao.hasFullAccess(pollData) && pollDao.isAttendee(pollData, delUser.toInt())) { + questionOwnerId = delUser.toInt() + answerTitle = translateMsg("poll.delegationAnswers") + userService.getUser(questionOwnerId).displayName + } else { + questionOwnerId = ThreadLocalUserContext.userId + answerTitle = translateMsg("poll.yourAnswers") + } + - val questionOwnerName = userService.getUser(questionOwnerId).displayName val pollDto = transformPollFromDB(pollData) - if (pollDto.state == PollDO.State.FINISHED) { - throw AccessException("access.exception.noAccess", "poll.error.closed") - } val layout = UILayout("poll.response.title") - val fieldset = UIFieldset(12, title = pollDto.title) - - - if (hasFullAccess(pollDto) && ThreadLocalUserContext.user?.id === questionOwnerId) { - val fieldSetDelegationUser = UIFieldset(title = "poll.userDelegation") - fieldSetDelegationUser.add( - UIInput( - id = "delegationUser", - label = "user", - dataType = UIDataType.USER - ) - ) - .add( - UIButton.createDefaultButton( - id = "response-poll-button", - responseAction = ResponseAction( - RestResolver.getRestUrl( - this::class.java, - "showDelegatedUser" - ), - targetType = TargetType.GET - ), - title = "poll.response.page" - ), - ) - layout.add(fieldSetDelegationUser) - } - if (hasFullAccess(pollDto)) { + if (pollDao.hasFullAccess(pollData)) { layout.add( MenuItem( "EDIT", @@ -109,20 +82,41 @@ class ResponsePageRest : AbstractDynamicPageRest() { type = MenuItemTargetType.REDIRECT ) ) - } - val fieldSet = UIFieldset(12, title = pollDto.title + " Antworten von " + questionOwnerName) - fieldSet - .add(UIReadOnlyField(value = pollDto.description, label = "Description")) - .add(UIReadOnlyField(value = pollDto.location, label = "Location")) - .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = "Owner")) - .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = "Deadline")) + if (!pollDto.isFinished() && ThreadLocalUserContext.userId === questionOwnerId) { + val fieldSetDelegationUser = UIFieldset(title = "poll.userDelegation") + fieldSetDelegationUser.add( + UIInput( + id = "delegationUser", + label = "user", + dataType = UIDataType.USER + ) + ) + .add(UISpacer()) + .add( + UIButton.createDefaultButton( + id = "response-poll-button", + responseAction = ResponseAction( + RestResolver.getRestUrl( + this::class.java, + "showDelegatedUser" + ), + targetType = TargetType.GET + ), + title = "poll.selectUser" + ), + ) + layout.add(fieldSetDelegationUser) + } + } + val fieldSet = UIFieldset(12, title = pollDto.title + " - " + answerTitle) fieldSet.add(UIReadOnlyField(value = pollDto.description, label = translateMsg("poll.description"))) .add(UIReadOnlyField(value = pollDto.location, label = translateMsg("poll.location"))) .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = translateMsg("poll.owner"))) .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = translateMsg("poll.deadline"))) - + .add(UISpacer()) + .add(UISpacer()) layout.add(fieldSet) @@ -175,7 +169,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { } else { col.add( UIRadioButton( - "responses[$index].answers[$index2]", + "responses[$index].answer", value = field.answers?.get(index2) ?: false, label = field.answers?.get(index2) ?: "" ) @@ -184,26 +178,46 @@ class ResponsePageRest : AbstractDynamicPageRest() { } } fieldSetQuestions.add(UIRow().add(col)) - layout.add(fieldSetQuestions) + fieldSet.add(fieldSetQuestions) } + val backUrl = if (returnToCaller.isNullOrEmpty()) { + PagesResolver.getListPageUrl(PollPageRest::class.java, absolute = true) + } else { + // Fix doubled encoding: + returnToCaller.replace("%2F", "/") + } layout.add( - UIButton.createDefaultButton( - id = "addResponse", - title = translateMsg("poll.respond"), + UIButton.createBackButton( responseAction = ResponseAction( - RestResolver.getRestUrl( - this::class.java, - "addResponse" - ) + "/?questionOwner=${questionOwnerId}", targetType = TargetType.POST - ) + backUrl, + targetType = TargetType.REDIRECT + ), + default = true ) ) + + if (!pollDto.isFinished()) { + layout.add( + UIButton.createDefaultButton( + id = "addResponse", + title = translateMsg("poll.respond"), + responseAction = ResponseAction( + RestResolver.getRestUrl( + this::class.java, + "addResponse" + ) + "/?questionOwner=${questionOwnerId}", targetType = TargetType.POST + ) + ) + ) + } + layout.watchFields.add("delegationUser") LayoutUtils.process(layout) return FormLayoutData(pollResponse, layout, createServerData(request)) } + @PostMapping("addResponse") fun addResponse( request: HttpServletRequest, @@ -238,24 +252,27 @@ class ResponsePageRest : AbstractDynamicPageRest() { ) } + @GetMapping("showDelegatedUser") fun showDelegatedUser( request: HttpServletRequest ): ResponseEntity? { return ResponseEntity.ok( ResponseAction( - url = "/react/response/dynamic/?pollId=${pollId}&questionOwner=${questionOwnerId}", + url = "/react/response/dynamic?pollId=${pollId}&questionOwner=${questionOwnerId}", targetType = TargetType.REDIRECT ) ) } + @PostMapping(RestPaths.WATCH_FIELDS) fun watchFields(@Valid @RequestBody postData: PostData): ResponseEntity { questionOwnerId = postData.data.delegationUser?.id return ResponseEntity.ok(ResponseAction(targetType = TargetType.UPDATE)) } + private fun transformPollFromDB(obj: PollDO): Poll { val poll = Poll() poll.copyFrom(obj) @@ -266,9 +283,4 @@ class ResponsePageRest : AbstractDynamicPageRest() { return poll } - private fun hasFullAccess(dto: Poll): Boolean { - val loggedInUser = ThreadLocalUserContext.user - val foundUser = dto.fullAccessUsers?.any { user -> user.id == loggedInUser?.id } - return foundUser == true || dto.owner?.id == loggedInUser?.id - } } \ No newline at end of file From 2ba964c762a1d95cfc4793528a589efd35287113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Mon, 19 Jun 2023 15:05:03 +0200 Subject: [PATCH 122/160] Changed cron job to real schedule --- .../src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt index 8cb3c04233..51efbbde3f 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt @@ -33,8 +33,7 @@ class PollCronJobs { /** * Cron job for daily stuff */ - // todo @Scheduled(cron = "0 0 1 * * *") // 1am everyday - @Scheduled(cron = "0 * * * * *") // Every Minute + @Scheduled(cron = "0 0 1 * * *") // 1am everyday fun dailyCronJobs() { cronDeletePolls() cronEndPolls() From 47ab6c076f18113f5e3230e2f3a34e9ea1986b15 Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Mon, 19 Jun 2023 15:21:29 +0200 Subject: [PATCH 123/160] bugfix --- .../src/main/resources/I18nResources.properties | 1 + .../main/resources/I18nResources_de.properties | 1 + .../projectforge/rest/poll/ResponsePageRest.kt | 15 ++++++++++----- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index 591301c986..28cd833c36 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -2013,6 +2013,7 @@ poll.response.page=Poll Response Page poll.response.title=Poll Response Page poll.userDelegation=User delegation poll.selectUser=Select user +poll.exception.noAttendee=This User is not part of the Poll poll.mail.endingSoon.subject=Poll ending in {0} days poll.mail.endingSoon.content=

Dear Attendees,

\

This is a friendly reminder that the poll "{0}" created by {1} is ending soon, on {2}. Please make sure to submit your responses before the deadline.

\ diff --git a/projectforge-business/src/main/resources/I18nResources_de.properties b/projectforge-business/src/main/resources/I18nResources_de.properties index 2cbd0df3be..1750b0ad1b 100644 --- a/projectforge-business/src/main/resources/I18nResources_de.properties +++ b/projectforge-business/src/main/resources/I18nResources_de.properties @@ -2103,6 +2103,7 @@ poll.response.page=Seite zur Umfrageantwort poll.response.title=Seite zur Umfrageantwort poll.userDelegation=Für andere Nutzer abstimmen poll.selectUser=Nutzer auswählen +poll.exception.noAttendee=Dieser Nutzer ist nicht Teil der Umfrage poll.mail.endingSoon.subject=Umfrage endet in {0} Tagen poll.mail.endingSoon.content=

Liebe Teilnehmerinnen und Teilnehmer,

\

wir möchten Sie daran erinnern, dass die Umfrage "{0}", erstellt von {1}, bald endet, nämlich am {2}. Bitte achten Sie darauf, Ihre Antworten vor dem Ablaufdatum einzureichen.

\ diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index d957243547..51845c929e 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -6,6 +6,7 @@ import org.projectforge.business.poll.PollDao import org.projectforge.business.poll.PollResponseDO import org.projectforge.business.poll.PollResponseDao import org.projectforge.business.user.service.UserService +import org.projectforge.framework.access.AccessException import org.projectforge.framework.i18n.translateMsg import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.framework.utils.NumberHelper @@ -257,12 +258,16 @@ class ResponsePageRest : AbstractDynamicPageRest() { fun showDelegatedUser( request: HttpServletRequest ): ResponseEntity? { - return ResponseEntity.ok( - ResponseAction( - url = "/react/response/dynamic?pollId=${pollId}&questionOwner=${questionOwnerId}", - targetType = TargetType.REDIRECT + var attendees = pollDao.internalGetById(pollId).attendeesIds + if (attendees != null && attendees.split(",").any { it.toIntOrNull() == questionOwnerId }){ + return ResponseEntity.ok( + ResponseAction( + url = "/react/response/dynamic?pollId=${pollId}&questionOwner=${questionOwnerId}", + targetType = TargetType.REDIRECT + ) ) - ) + } + else throw AccessException("poll.exception.noAttendee") } From 414b0e1c8e3d247070543968d7b9de86cebc54e5 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Mon, 19 Jun 2023 16:15:22 +0200 Subject: [PATCH 124/160] fix singel select --- .../org/projectforge/rest/poll/ResponsePageRest.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 51845c929e..38a2fa1a36 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -1,6 +1,7 @@ package org.projectforge.rest.poll import com.fasterxml.jackson.databind.ObjectMapper +import org.projectforge.business.calendar.event.model.SeriesModificationMode import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao import org.projectforge.business.poll.PollResponseDO @@ -170,8 +171,8 @@ class ResponsePageRest : AbstractDynamicPageRest() { } else { col.add( UIRadioButton( - "responses[$index].answer", - value = field.answers?.get(index2) ?: false, + "responses[$index].answers[0]", + value = field.answers?.get(index2) ?: "", label = field.answers?.get(index2) ?: "" ) ) @@ -259,15 +260,14 @@ class ResponsePageRest : AbstractDynamicPageRest() { request: HttpServletRequest ): ResponseEntity? { var attendees = pollDao.internalGetById(pollId).attendeesIds - if (attendees != null && attendees.split(",").any { it.toIntOrNull() == questionOwnerId }){ + if (attendees != null && attendees.split(",").any { it.toIntOrNull() == questionOwnerId }) { return ResponseEntity.ok( ResponseAction( url = "/react/response/dynamic?pollId=${pollId}&questionOwner=${questionOwnerId}", targetType = TargetType.REDIRECT ) ) - } - else throw AccessException("poll.exception.noAttendee") + } else throw AccessException("poll.exception.noAttendee") } From 43505cc43f5a7efd31446fa5f690c825f70f6935 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Tue, 20 Jun 2023 12:10:52 +0200 Subject: [PATCH 125/160] fix excelEcport an remove unsued couter --- .../projectforge/rest/poll/PollPageRest.kt | 2 - .../rest/poll/excel/ExcelExport.kt | 60 ++++++++++++++++++- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index faddaf8169..e76c336ebd 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -371,11 +371,9 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. User.restoreDisplayNames(users, userService) val allUsers = dto.attendees?.toMutableList() ?: mutableListOf() - var counter = 0 users?.forEach { user -> if (allUsers.none { it.id == user.id }) { allUsers.add(user) - counter++ } } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt index 036648f6ab..41cc68cf14 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt @@ -6,11 +6,14 @@ import de.micromata.merlin.excel.ExcelWorkbook import org.apache.poi.ss.usermodel.CellStyle import org.apache.poi.ss.usermodel.HorizontalAlignment import org.apache.poi.ss.util.CellRangeAddress +import org.projectforge.business.group.service.GroupService import org.projectforge.business.poll.PollResponseDao +import org.projectforge.business.user.service.UserService import org.projectforge.rest.dto.User import org.projectforge.rest.poll.Poll import org.projectforge.rest.poll.types.BaseType import org.projectforge.rest.poll.types.PollResponse +import org.projectforge.web.rest.converter.PFUserDOConverter import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired @@ -29,6 +32,12 @@ class ExcelExport { @Autowired private lateinit var pollResponseDao: PollResponseDao + @Autowired + private lateinit var groupService: GroupService + + @Autowired + private lateinit var userService: UserService + fun getExcel(poll: Poll): ByteArray? { val responses = pollResponseDao.internalLoadAll().filter { it.poll?.id == poll.id } @@ -43,14 +52,57 @@ class ExcelExport { var anzNewRows = 2 anzNewRows += (poll.attendees?.size ?: 0) + + createNewRow(excelSheet, emptyRow, anzNewRows) setFirstRow(excelSheet, style, poll) + + + poll.attendees?.sortedBy { it.displayName } poll.attendees?.forEachIndexed { index, user -> val res = PollResponse() responses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } setNewRows(excelSheet, poll, user, res, index) } + + + var fullAccessUser = poll.fullAccessUsers?.toMutableList() ?: mutableListOf() + val accesGroupIds = poll.fullAccessGroups?.filter { it.id != null }?.map { it.id!! }?.toIntArray() + val accessUserIds = UserService().getUserIds(groupService.getGroupUsers(accesGroupIds)) + val accessUsers = User.toUserList(accessUserIds) + User.restoreDisplayNames(accessUsers, userService) + + accessUsers?.forEach { user -> + if (fullAccessUser.none { it.id == user.id }) { + fullAccessUser.add(user) + } + } + + var owner = User.getUser(poll.owner?.id, false) + if (owner != null) { + fullAccessUser.add(owner) + } + + fullAccessUser.forEachIndexed { index, user -> + var number = index + (poll.attendees?.size ?: 0) + if (poll.attendees?.map { it.id }?.contains(user.id) == false) { + val res = PollResponse() + responses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } + // User add a Response + if (res.id != null) { + // create A new Row emptyRow + Objects.requireNonNull( + excelSheet.getRow(FIRST_DATA_ROW_NUM) + ).copyAndInsert( + emptyRow.sheet + ) + // Put Data's in the Row + setNewRows(excelSheet, poll, user, res, number) + } + } + } + return returnByteFile(excelSheet) } } catch (e: NullPointerException) { @@ -108,11 +160,12 @@ class ExcelExport { } questionAnswer?.answers?.forEachIndexed { ind, antwort -> cell++ - if (question.type == BaseType.SingleResponseQuestion || question.type == BaseType.MultiResponseQuestion) { + if (question.type == BaseType.MultiResponseQuestion) { if (antwort is Boolean && antwort == true) { excelRow.getCell(cell).setCellValue("X") } - if (antwort is String && antwort == question.answers?.get(ind)) { + } else if (question.type == BaseType.SingleResponseQuestion) { + if (antwort is String && antwort.equals(question.answers?.get(ind))) { excelRow.getCell(cell).setCellValue("X") } } else { @@ -129,7 +182,8 @@ class ExcelExport { var counterOfBreaking = 0 var counterOfOverlength = 0 - val pufferSplit: Array = puffer.split("\r\n|\r|\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val pufferSplit: Array = + puffer.split("\r\n|\r|\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() // check for line-breaks for (i in pufferSplit.indices) { counterOfBreaking++ From 948b59ad37c97e7d73c024086221537daeb0c974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 20 Jun 2023 13:50:02 +0200 Subject: [PATCH 126/160] Small fixes --- .../main/resources/I18nResources.properties | 2 +- .../resources/I18nResources_de.properties | 2 +- .../projectforge/rest/poll/PollPageRest.kt | 2 +- .../rest/poll/ResponsePageRest.kt | 109 +++++++----------- 4 files changed, 42 insertions(+), 73 deletions(-) diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index 1f9550dff3..45e3a5d1ac 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -2013,7 +2013,7 @@ poll.response.page=Poll Response Page poll.response.title=Poll Response Page poll.userDelegation=User delegation poll.selectUser=Select user -poll.exception.noAttendee=This User is not part of the Poll +poll.exception.noAttendee=This user is not part of the poll. poll.mail.endingSoon.subject=Poll ending in {0} days poll.mail.endingSoon.content=

Dear Attendees,

\

This is a friendly reminder that the poll "{0}" created by {1} is ending soon, on {2}. Please make sure to submit your responses before the deadline.

\ diff --git a/projectforge-business/src/main/resources/I18nResources_de.properties b/projectforge-business/src/main/resources/I18nResources_de.properties index 1750b0ad1b..53ab07709b 100644 --- a/projectforge-business/src/main/resources/I18nResources_de.properties +++ b/projectforge-business/src/main/resources/I18nResources_de.properties @@ -2103,7 +2103,7 @@ poll.response.page=Seite zur Umfrageantwort poll.response.title=Seite zur Umfrageantwort poll.userDelegation=Für andere Nutzer abstimmen poll.selectUser=Nutzer auswählen -poll.exception.noAttendee=Dieser Nutzer ist nicht Teil der Umfrage +poll.exception.noAttendee=Dieser Nutzer ist nicht Teil der Umfrage. poll.mail.endingSoon.subject=Umfrage endet in {0} Tagen poll.mail.endingSoon.content=

Liebe Teilnehmerinnen und Teilnehmer,

\

wir möchten Sie daran erinnern, dass die Umfrage "{0}", erstellt von {1}, bald endet, nämlich am {2}. Bitte achten Sie darauf, Ihre Antworten vor dem Ablaufdatum einzureichen.

\ diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 352bbfe8ed..b6fa8cf234 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -158,7 +158,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val layout = super.createEditLayout(dto, userAccess) val fieldset = UIFieldset(UILength(12)) layout.add(fieldset) - if (dto.state == PollDO.State.RUNNING && dto.isAlreadyCreated()) { + if (dto.isFinished() && dto.isAlreadyCreated()) { layout.add( MenuItem( "export-poll-response-button", diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 33031b10a6..1352052bab 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -1,11 +1,11 @@ package org.projectforge.rest.poll import com.fasterxml.jackson.databind.ObjectMapper -import org.projectforge.business.calendar.event.model.SeriesModificationMode import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollDao import org.projectforge.business.poll.PollResponseDO import org.projectforge.business.poll.PollResponseDao +import org.projectforge.business.poll.filter.PollAssignment import org.projectforge.business.user.service.UserService import org.projectforge.framework.access.AccessException import org.projectforge.framework.i18n.translateMsg @@ -75,47 +75,12 @@ class ResponsePageRest : AbstractDynamicPageRest() { throw AccessException("access.exception.noAccess", "Umfrage nicht gefunden.") } - if (!pollData.getPollAssignment().contains(PollAssignment.ATTENDEE)) { + // allow access to attendee, full access and owner + if (pollData.getPollAssignment().contains(PollAssignment.OTHER)) { throw AccessException("access.exception.noAccess", "Du darfst nicht auf diese Umfrage antworten.") } val layout = UILayout("poll.response.title") - val fieldset = UIFieldset(12, title = pollDto.title) - - - if (pollDto.isFinished() == false && pollDto.isAlreadyCreated() && pollDao.hasFullAccess(pollData)) { - fieldset.add( - UIButton.createExportButton( - id = "export-poll-response-button", - responseAction = ResponseAction( - "${Rest.URL}/poll/export/${pollDto.id}", - targetType = TargetType.POST - ), - title = "poll.export.response.poll" - ) - ) - } - fieldset.add( - UIRow().add( - UICol( - UILength(10) - ) - ).add( - UICol( - UILength(1) - ).add( - UIButton.createLinkButton( - id = "poll-guide", title = "poll.guide", responseAction = ResponseAction( - PagesResolver.getDynamicPageUrl( - PollInfoPageRest::class.java, absolute = true - ), targetType = TargetType.MODAL - ) - ) - ) - ) - ) - - if (pollDao.hasFullAccess(pollData)) { layout.add( MenuItem( @@ -125,43 +90,42 @@ class ResponsePageRest : AbstractDynamicPageRest() { type = MenuItemTargetType.REDIRECT ) ) - - if (!pollDto.isFinished() && ThreadLocalUserContext.userId === questionOwnerId) { - val fieldSetDelegationUser = UIFieldset(title = "poll.userDelegation") - fieldSetDelegationUser.add( - UIInput( - id = "delegationUser", - label = "user", - dataType = UIDataType.USER - ) - ) - .add(UISpacer()) - .add( - UIButton.createDefaultButton( - id = "response-poll-button", - responseAction = ResponseAction( - RestResolver.getRestUrl( - this::class.java, - "showDelegatedUser" - ), - targetType = TargetType.GET - ), - title = "poll.selectUser" - ), - ) - layout.add(fieldSetDelegationUser) - } } - val fieldSet = UIFieldset(12, title = pollDto.title + " - " + answerTitle) - fieldSet.add(UIReadOnlyField(value = pollDto.description, label = translateMsg("poll.description"))) + val fieldset = UIFieldset(12, title = pollDto.title + " - " + answerTitle) + layout.add(fieldset) + fieldset.add(UIReadOnlyField(value = pollDto.description, label = translateMsg("poll.description"))) .add(UIReadOnlyField(value = pollDto.location, label = translateMsg("poll.location"))) .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = translateMsg("poll.owner"))) .add(UIReadOnlyField(value = pollDto.deadline.toString(), label = translateMsg("poll.deadline"))) .add(UISpacer()) .add(UISpacer()) - layout.add(fieldSet) + if (!pollDto.isFinished() && ThreadLocalUserContext.userId === questionOwnerId) { + val fieldSetDelegationUser = UIFieldset(title = "poll.userDelegation") + fieldSetDelegationUser.add( + UIInput( + id = "delegationUser", + label = "user", + dataType = UIDataType.USER + ) + ) + .add(UISpacer()) + .add( + UIButton.createDefaultButton( + id = "response-poll-button", + responseAction = ResponseAction( + RestResolver.getRestUrl( + this::class.java, + "showDelegatedUser" + ), + targetType = TargetType.GET + ), + title = "poll.selectUser" + ), + ) + layout.add(fieldSetDelegationUser) + } val pollResponse = PollResponse() pollResponse.poll = pollData @@ -221,7 +185,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { } } fieldSetQuestions.add(UIRow().add(col)) - fieldSet.add(fieldSetQuestions) + fieldset.add(fieldSetQuestions) } val backUrl = if (returnToCaller.isNullOrEmpty()) { @@ -300,8 +264,13 @@ class ResponsePageRest : AbstractDynamicPageRest() { fun showDelegatedUser( request: HttpServletRequest ): ResponseEntity? { - var attendees = pollDao.internalGetById(pollId).attendeesIds - if (attendees != null && attendees.split(",").any { it.toIntOrNull() == questionOwnerId }) { + var attendees = listOf( + pollDao.internalGetById(pollId).attendeeIds, + pollDao.internalGetById(pollId).fullAccessUserIds, + pollDao.internalGetById(pollId).owner?.id + ) + var joinedAttendeeIds = attendees.joinToString(", ") + if (attendees != null && joinedAttendeeIds.split(", ").any { it.toIntOrNull() == questionOwnerId }) { return ResponseEntity.ok( ResponseAction( url = "/react/response/dynamic?pollId=${pollId}&questionOwner=${questionOwnerId}", From a9ee1cacb9861c2f62f67c7b330cb183668aba97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 20 Jun 2023 14:55:23 +0200 Subject: [PATCH 127/160] Commentted menu item for poll --- .../main/kotlin/org/projectforge/menu/builder/MenuCreator.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuCreator.kt b/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuCreator.kt index c86ef61179..4341e2938d 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuCreator.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/menu/builder/MenuCreator.kt @@ -36,7 +36,6 @@ import org.projectforge.business.orga.ContractDao import org.projectforge.business.orga.PostausgangDao import org.projectforge.business.orga.PosteingangDao import org.projectforge.business.orga.VisitorbookDao -import org.projectforge.business.poll.PollDao import org.projectforge.business.sipgate.SipgateConfiguration import org.projectforge.business.user.ProjectForgeGroup import org.projectforge.business.user.UserRightValue @@ -450,7 +449,7 @@ open class MenuCreator { requiredUserRightId = ContractDao.USER_RIGHT_ID, requiredUserRightValues = READONLY_READWRITE ) ) - .add(MenuItemDef(MenuItemDefId.POLL)) + //.add(MenuItemDef(MenuItemDefId.POLL)) .add( MenuItemDef( MenuItemDefId.VISITORBOOK, From 02ff63819847ffc325e9c889d67179bc47e6b967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 20 Jun 2023 15:02:35 +0200 Subject: [PATCH 128/160] Reverted mail hostname and port --- .../src/main/resources/application.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projectforge-business/src/main/resources/application.properties b/projectforge-business/src/main/resources/application.properties index 3dfed6b30d..782c9785d0 100644 --- a/projectforge-business/src/main/resources/application.properties +++ b/projectforge-business/src/main/resources/application.properties @@ -247,9 +247,9 @@ mail.session.pfmailsession.standardEmailSender=sender@yourserver.org #Mail protocol: Plain, StartTLS,SSL mail.session.pfmailsession.encryption=Plain #Hostname of the email server -mail.session.pfmailsession.smtp.host=localhost +mail.session.pfmailsession.smtp.host=mail.yourserver.org #Port number of the email server -mail.session.pfmailsession.smtp.port=1025 +mail.session.pfmailsession.smtp.port=25 #The email server needs authentification mail.session.pfmailsession.smtp.auth=false #Authentification by user name From 09f6619086445721ee0b0c9708283aa7e36c43fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 20 Jun 2023 15:14:35 +0200 Subject: [PATCH 129/160] Corrected variable in mail --- .../src/main/resources/I18nResources.properties | 2 +- .../main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index a65642f868..eeb54b0592 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -2016,7 +2016,7 @@ poll.response.mail.update.content=

Dear {0},

\

I wanted to inform you that Person {2} has updated their answer to Poll {1}.

\

If you were not notified about this,

\

I recommend reaching out to the person directly or adjusting your results accordingly.

\ -

You can modify your response until "{4}".

\ +

You can modify your response until "{3}".

\

Best regards,

\

{2}

poll.userDelegation=User delegation diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index b8ea5031ec..3da940693d 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -29,7 +29,6 @@ import org.springframework.web.bind.annotation.* import java.time.format.DateTimeFormatter import java.util.* import javax.servlet.http.HttpServletRequest -import kotlin.collections.ArrayList import javax.validation.Valid @@ -254,7 +253,6 @@ class ResponsePageRest : AbstractDynamicPageRest() { ) } - pollResponseDao.saveOrUpdate(pollResponseDO) if (ThreadLocalUserContext.user != pollResponseDO.owner) { From 8984e397361c396f2edcd8fca29137eb279f90b2 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 21 Jul 2023 08:45:46 +0200 Subject: [PATCH 130/160] fix excel --- .../org/projectforge/rest/poll/PollPageRest.kt | 9 ++++++++- .../projectforge/rest/poll/excel/ExcelExport.kt | 17 ++++++----------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 43a92c3902..3a555d3930 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -17,6 +17,7 @@ import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.framework.persistence.api.QueryFilter import org.projectforge.framework.persistence.api.impl.CustomResultFilter import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext +import org.projectforge.framework.persistence.user.entities.PFUserDO import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType import org.projectforge.rest.config.Rest @@ -296,8 +297,14 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun onAfterSaveOrUpdate(request: HttpServletRequest, obj: PollDO, postData: PostData) { // add all attendees mails - val mailTo: ArrayList = + + var mailTo: ArrayList = ArrayList(postData.data.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) + var a = User.toIntList(postData.data.attendees) + + var b = userService.getSortedUsers(a) + + mailTo = ArrayList(b.map { it.email }) val owner = userService.getUser(obj.owner?.id) val mailFrom = owner?.email.toString() val mailSubject: String diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt index 41cc68cf14..5cc6c49d31 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt @@ -63,7 +63,7 @@ class ExcelExport { poll.attendees?.forEachIndexed { index, user -> val res = PollResponse() responses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } - setNewRows(excelSheet, poll, user, res, index) + setNewRows(excelSheet, poll, user, res, index + FIRST_DATA_ROW_NUM) } @@ -83,20 +83,14 @@ class ExcelExport { if (owner != null) { fullAccessUser.add(owner) } - + User.restoreDisplayNames(fullAccessUser, userService) fullAccessUser.forEachIndexed { index, user -> - var number = index + (poll.attendees?.size ?: 0) + var number = (index) + (anzNewRows) if (poll.attendees?.map { it.id }?.contains(user.id) == false) { val res = PollResponse() responses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } // User add a Response if (res.id != null) { - // create A new Row emptyRow - Objects.requireNonNull( - excelSheet.getRow(FIRST_DATA_ROW_NUM) - ).copyAndInsert( - emptyRow.sheet - ) // Put Data's in the Row setNewRows(excelSheet, poll, user, res, number) } @@ -144,8 +138,9 @@ class ExcelExport { excelRow.setHeight(30F) } - private fun setNewRows(excelSheet: ExcelSheet, poll: Poll, user: User, res: PollResponse?, index: Int) { - val excelRow = excelSheet.getRow(FIRST_DATA_ROW_NUM + index) + private fun setNewRows(excelSheet: ExcelSheet, poll: Poll, user: User, res: PollResponse?, rowNumber: Int) { + val excelRow = excelSheet.getRow(rowNumber) + excelRow.getCell(0).setCellValue(user.displayName) excelSheet.autosize(0) From 5cfd12067b5a49d8747f0e3edbf64541afd6c21a Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Fri, 21 Jul 2023 09:54:44 +0200 Subject: [PATCH 131/160] Reformat code, some logging, i18n & Source Headers --- .../src/main/resources/i18nKeys.json | 496 ++++++++++-------- .../org/projectforge/business/poll/PollDO.kt | 38 +- .../org/projectforge/business/poll/PollDao.kt | 33 +- .../business/poll/PollResponseDO.kt | 25 +- .../business/poll/PollResponseDao.kt | 25 +- .../business/poll/filter/PollAssignment.kt | 25 +- .../poll/filter/PollAssignmentFilter.kt | 25 +- .../business/poll/filter/PollState.kt | 25 +- .../business/poll/filter/PollStateFilter.kt | 23 + .../main/resources/I18nResources.properties | 466 ++++++++-------- .../resources/I18nResources_de.properties | 494 ++++++++--------- .../kotlin/org/projectforge/rest/poll/Poll.kt | 25 +- .../projectforge/rest/poll/PollCronJobs.kt | 43 +- .../rest/poll/PollInfoPageRest.kt | 41 +- .../projectforge/rest/poll/PollMailService.kt | 36 +- .../projectforge/rest/poll/PollPageRest.kt | 31 +- .../rest/poll/ResponsePageRest.kt | 43 +- .../rest/poll/excel/ExcelExport.kt | 29 +- .../rest/poll/types/PollResponse.kt | 25 +- .../rest/poll/types/PremadeQuestions.kt | 23 + .../projectforge/rest/poll/types/Question.kt | 25 +- 21 files changed, 1263 insertions(+), 733 deletions(-) diff --git a/projectforge-application/src/main/resources/i18nKeys.json b/projectforge-application/src/main/resources/i18nKeys.json index ae6661a67b..acddf58278 100644 --- a/projectforge-application/src/main/resources/i18nKeys.json +++ b/projectforge-application/src/main/resources/i18nKeys.json @@ -160,11 +160,11 @@ {"i18nKey":"StringValidator.maximum","bundleName":"I18nResources","translation":"The field ''${label}'' with ${length} characters is longer than the maximum of ${maximum} characters.","translationDE":"Das Feld ''${label}'' mit ${length} Zeichen darf maximal ${maximum} Zeichen lang sein. ","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"StringValidator.minimum","bundleName":"I18nResources","translation":"The field ''${label}'' with ${length} characters is shorter than the minimum of ${minimum} characters.","translationDE":"Das Feld ''${label}'' mit ${length} Zeichen muss mindestens ${minimum} Zeichen lang sein.","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"StringValidator.range","bundleName":"I18nResources","translation":"The field ''${label}'' with ${length} characters is not between ${minimum} and ${maximum} characters long.","translationDE":"Das Feld ''${label}'' mit ${length} Zeichen muss zwischen ${minimum} und ${maximum} Zeichen lang sein.","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"access","bundleName":"I18nResources","translation":"Accessright","translationDE":"Zugriffsrecht","usedInClasses":["org.projectforge.framework.persistence.DaoConst","org.projectforge.registry.Registry","org.projectforge.rest.scripting.ScriptPagesRest","org.projectforge.web.access.AccessEditPage","org.projectforge.web.access.AccessListPage"],"usedInFiles":[]}, + {"i18nKey":"access","bundleName":"I18nResources","translation":"Accessright","translationDE":"Zugriffsrecht","usedInClasses":["org.projectforge.business.poll.filter.PollAssignment","org.projectforge.framework.persistence.DaoConst","org.projectforge.registry.Registry","org.projectforge.rest.scripting.ScriptPagesRest","org.projectforge.web.access.AccessEditPage","org.projectforge.web.access.AccessListPage"],"usedInFiles":[]}, {"i18nKey":"access.accessTable","bundleName":"I18nResources","translation":"Access table","translationDE":"Zugriffstabelle","usedInClasses":["org.projectforge.web.access.AccessEditForm"],"usedInFiles":[]}, {"i18nKey":"access.exception.demoUserHasNoAccess","bundleName":"I18nResources","translation":"The demo user is blocked for this action.","translationDE":"Der oder die Demobenutzer:in ist für diese Aktion gesperrt.","usedInClasses":["org.projectforge.framework.access.AccessCheckerImpl"],"usedInFiles":[]}, {"i18nKey":"access.exception.employeeHasNoVacationDays","bundleName":"I18nResources","translation":"The employee has no vacation days assigned","translationDE":"Dem oder der Mitarbeiter:in sind keine Urlaubstage zugewiesen","usedInClasses":["org.projectforge.business.vacation.service.VacationService"],"usedInFiles":[]}, - {"i18nKey":"access.exception.noAccess","bundleName":"I18nResources","translation":"No access.","translationDE":"Kein Zugriff.","usedInClasses":["org.projectforge.framework.persistence.api.BaseDaoSupport"],"usedInFiles":[]}, + {"i18nKey":"access.exception.noAccess","bundleName":"I18nResources","translation":"No access.","translationDE":"Kein Zugriff.","usedInClasses":["org.projectforge.framework.persistence.api.BaseDaoSupport","org.projectforge.rest.poll.ResponsePageRest"],"usedInFiles":[]}, {"i18nKey":"access.exception.noEmployeeToUser","bundleName":"I18nResources","translation":"The user has no employee assigned","translationDE":"Angemeldete:r Benutzer:in hat keinen zugehörige:n Mitarbeiter:in","usedInClasses":["org.projectforge.business.vacation.service.VacationService"],"usedInFiles":[]}, {"i18nKey":"access.exception.noReadAccess","bundleName":"I18nResources","translation":"No read-access.","translationDE":"Kein lesender Zugriff.","usedInClasses":["org.projectforge.business.common.BaseUserGroupRightUtils"],"usedInFiles":[]}, {"i18nKey":"access.exception.noUserGiven","bundleName":"I18nResources","translation":"No logged-in user given.","translationDE":"Keine:n angemeldete:n Benutzer:in gefunden.","usedInClasses":["org.projectforge.framework.access.AccessCheckerImpl"],"usedInFiles":[]}, @@ -491,15 +491,15 @@ {"i18nKey":"attachment.zip.standard","bundleName":"I18nResources","translation":"No encryption","translationDE":"ohne Verschlüsselung","usedInClasses":["org.projectforge.jcr.ZipMode"],"usedInFiles":[]}, {"i18nKey":"attachments","bundleName":"I18nResources","translation":"Attachments","translationDE":"Anhänge","usedInClasses":["org.projectforge.framework.jcr.AttachmentsService","org.projectforge.plugins.datatransfer.DataTransferNotificationMailService","org.projectforge.plugins.merlin.rest.MerlinAttachmentsActionListener","org.projectforge.ui.UIAgGridColumnDef","org.projectforge.ui.UIAttachmentList","org.projectforge.web.fibu.AuftragEditForm"],"usedInFiles":[]}, {"i18nKey":"attachments.short","bundleName":"I18nResources","translation":"Att.","translationDE":"Anh.","usedInClasses":["org.projectforge.web.fibu.AuftragListPage"],"usedInFiles":[]}, - {"i18nKey":"attr.deletemodal.heading","bundleName":"I18nResources","translation":"Would you like to delete this entry?","translationDE":"Soll dieser Eintrag wirklich gelöscht werden?","usedInClasses":["org.projectforge.web.common.timeattr.TimedAttributePanel"],"usedInFiles":[]}, - {"i18nKey":"attr.deletemodal.question","bundleName":"I18nResources","translation":"Yes: This entry will be deleted and all changes on this page will be saved.
Cancel: This entry will not be deleted and you stay on this page.","translationDE":"Ja: Der Eintrag wird gelöscht und alle Änderungen auf dieser Seite werden gespeichert.
Abbrechen: Der Eintrag wird nicht gelöscht und Sie bleiben auf dieser Seite.","usedInClasses":["org.projectforge.web.common.timeattr.TimedAttributePanel"],"usedInFiles":[]}, + {"i18nKey":"attr.deletemodal.heading","bundleName":"I18nResources","translation":"Would you like to delete this entry?","translationDE":"Soll dieser Eintrag wirklich gelöscht werden?","usedInClasses":["org.projectforge.web.common.timeattr.TimedAttributePanel"],"usedInFiles":[]}, + {"i18nKey":"attr.deletemodal.question","bundleName":"I18nResources","translation":"Yes: This entry will be deleted and all changes on this page will be saved.
Cancel: This entry will not be deleted and you stay on this page.","translationDE":"Ja: Der Eintrag wird gelöscht und alle Änderungen auf dieser Seite werden gespeichert.
Abbrechen: Der Eintrag wird nicht gelöscht und Sie bleiben auf dieser Seite.","usedInClasses":["org.projectforge.web.common.timeattr.TimedAttributePanel"],"usedInFiles":[]}, {"i18nKey":"attr.instantOfTime","bundleName":"I18nResources","translation":"Instant of time","translationDE":"Zeitpunkt","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"attr.savemodal.heading","bundleName":"I18nResources","translation":"Would you like to save the changes?","translationDE":"Soll die Änderungen gespeichert werden?","usedInClasses":["org.projectforge.web.common.timeattr.TimedAttributePanel"],"usedInFiles":[]}, - {"i18nKey":"attr.savemodal.question","bundleName":"I18nResources","translation":"Yes: All changes on this page will be saved.
Cancel: The changes will not be saved but not discarded and you stay on this page.","translationDE":"Ja: Alle Änderungen auf dieser Seite werden gespeichert.
Abbrechen: Die Änderungen werden nicht gespeichert aber auch nicht verworfen und Sie bleiben auf dieser Seite.","usedInClasses":["org.projectforge.web.common.timeattr.TimedAttributePanel"],"usedInFiles":[]}, + {"i18nKey":"attr.savemodal.heading","bundleName":"I18nResources","translation":"Would you like to save the changes?","translationDE":"Soll die Änderungen gespeichert werden?","usedInClasses":["org.projectforge.web.common.timeattr.TimedAttributePanel"],"usedInFiles":[]}, + {"i18nKey":"attr.savemodal.question","bundleName":"I18nResources","translation":"Yes: All changes on this page will be saved.
Cancel: The changes will not be saved but not discarded and you stay on this page.","translationDE":"Ja: Alle Änderungen auf dieser Seite werden gespeichert.
Abbrechen: Die Änderungen werden nicht gespeichert aber auch nicht verworfen und Sie bleiben auf dieser Seite.","usedInClasses":["org.projectforge.web.common.timeattr.TimedAttributePanel"],"usedInFiles":[]}, {"i18nKey":"attr.starttime.alreadyexists.day","bundleName":"I18nResources","translation":"There is already an entry with the same date (Day).","translationDE":"Es existiert bereits ein Eintrag mit gleichem Datum (Tag).","usedInClasses":["org.projectforge.web.common.timeattr.TimedAttributePanel"],"usedInFiles":[]}, {"i18nKey":"attr.starttime.alreadyexists.month","bundleName":"I18nResources","translation":"There is already an entry with the same date (Month).","translationDE":"Es existiert bereits ein Eintrag mit gleichem Datum (Monat).","usedInClasses":["org.projectforge.web.common.timeattr.TimedAttributePanel"],"usedInFiles":[]}, - {"i18nKey":"attr.validFrom","bundleName":"I18nResources","translation":"Valid from","translationDE":"Gültig ab","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"back","bundleName":"I18nResources","translation":"Back","translationDE":"Zurück","usedInClasses":["org.projectforge.ui.UIButton","org.projectforge.ui.UILayout","org.projectforge.web.fibu.EingangsrechnungListForm"],"usedInFiles":[]}, + {"i18nKey":"attr.validFrom","bundleName":"I18nResources","translation":"Valid from","translationDE":"Gültig ab","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"back","bundleName":"I18nResources","translation":"Back","translationDE":"Zurück","usedInClasses":["org.projectforge.ui.UIButton","org.projectforge.ui.UILayout","org.projectforge.web.address.PhoneCallForm","org.projectforge.web.fibu.EingangsrechnungListForm"],"usedInFiles":[]}, {"i18nKey":"bicvalidator.wronglength","bundleName":"I18nResources","translation":"The field ''${label}'' must be 8 or 11 characters long.","translationDE":"Das Feld ''${label}'' muss 8 oder 11 Zeichen lang sein.","usedInClasses":["org.projectforge.web.common.BicValidator"],"usedInFiles":[]}, {"i18nKey":"birthday","bundleName":"I18nResources","translation":"Birthday","translationDE":"Geburtstag","usedInClasses":["org.projectforge.business.address.AddressExport","org.projectforge.business.address.BirthdayCache","org.projectforge.flyway.dbmigration.V7_0_0_6__MigrateEmployeeAndCarryVacationDays","org.projectforge.rest.AddressPagesRest","org.projectforge.rest.MyAccountPageRest","org.projectforge.rest.fibu.EmployeePagesRest","org.projectforge.web.address.AddressPageSupport","org.projectforge.web.fibu.EmployeeEditForm"],"usedInFiles":[]}, {"i18nKey":"book.abstract","bundleName":"I18nResources","translation":"Abstract","translationDE":"Zusammenfassung","usedInClasses":["org.projectforge.business.book.BookDO"],"usedInFiles":[]}, @@ -746,13 +746,13 @@ {"i18nKey":"contextMenu.cancel","bundleName":"I18nResources","translation":"Cancel","translationDE":"Abbrechen","usedInClasses":["org.projectforge.web.wicket.AbstractUnsecureBasePage"],"usedInFiles":[]}, {"i18nKey":"contextMenu.newTab","bundleName":"I18nResources","translation":"Open in new tab","translationDE":"Link in neuem Tab öffnen","usedInClasses":["org.projectforge.web.wicket.AbstractUnsecureBasePage"],"usedInFiles":[]}, {"i18nKey":"copy","bundleName":"I18nResources","translation":"Copy","translationDE":"Kopieren","usedInClasses":["org.projectforge.web.teamcal.integration.TeamCalCalendarPanel"],"usedInFiles":["./projectforge-wicket/src/main/java/org/projectforge/web/gantt/GanttChartEditTreeTablePanel.html"]}, - {"i18nKey":"create","bundleName":"I18nResources","translation":"Create","translationDE":"Anlegen","usedInClasses":["org.projectforge.framework.persistence.database.SchemaExport","org.projectforge.rest.GroupPagesRest","org.projectforge.rest.TimesheetFavoritesRest","org.projectforge.rest.UserPagesRest","org.projectforge.rest.calendar.CalendarServicesRest","org.projectforge.rest.task.TaskFavoritesRest","org.projectforge.ui.LayoutUtils","org.projectforge.ui.UIButton","org.projectforge.web.fibu.AbstractRechnungEditForm","org.projectforge.web.user.GroupEditForm","org.projectforge.web.user.UserEditForm","org.projectforge.web.wicket.AbstractEditForm"],"usedInFiles":[]}, + {"i18nKey":"create","bundleName":"I18nResources","translation":"Create","translationDE":"Anlegen","usedInClasses":["org.projectforge.framework.persistence.database.SchemaExport","org.projectforge.rest.GroupPagesRest","org.projectforge.rest.TimesheetFavoritesRest","org.projectforge.rest.UserPagesRest","org.projectforge.rest.calendar.CalendarServicesRest","org.projectforge.rest.poll.PollPageRest","org.projectforge.rest.task.TaskFavoritesRest","org.projectforge.ui.LayoutUtils","org.projectforge.ui.UIButton","org.projectforge.web.fibu.AbstractRechnungEditForm","org.projectforge.web.user.GroupEditForm","org.projectforge.web.user.UserEditForm","org.projectforge.web.wicket.AbstractEditForm"],"usedInFiles":[]}, {"i18nKey":"created","bundleName":"I18nResources","translation":"created","translationDE":"angelegt","usedInClasses":["org.projectforge.business.address.AddressDao","org.projectforge.business.fibu.AuftragDO","org.projectforge.business.fibu.EingangsrechnungDO","org.projectforge.business.fibu.RechnungDO","org.projectforge.business.fibu.kost.KostZuweisungExport","org.projectforge.business.humanresources.HRPlanningDO","org.projectforge.business.teamcal.event.model.CalEventDO","org.projectforge.business.teamcal.event.model.TeamEventDO","org.projectforge.business.timesheet.TimesheetExport","org.projectforge.flyway.dbmigration.V7_0_0_6__MigrateEmployeeAndCarryVacationDays","org.projectforge.flyway.dbmigration.V7_4_1_3__ReleaseUserPassword","org.projectforge.framework.persistence.entities.AbstractBaseDO","org.projectforge.framework.persistence.entities.AbstractHistorizableBaseDO","org.projectforge.framework.renderer.PdfRenderer","org.projectforge.jcr.RepoService","org.projectforge.mail.SendMail","org.projectforge.plugins.datatransfer.rest.DataTransferAreaPagesRest","org.projectforge.plugins.licensemanagement.LicenseListPage","org.projectforge.plugins.marketing.AddressCampaignListPage","org.projectforge.plugins.marketing.AddressCampaignValueListPage","org.projectforge.plugins.marketing.rest.AddressCampaignPagesRest","org.projectforge.plugins.memo.MemoListPage","org.projectforge.plugins.memo.rest.MemoPagesRest","org.projectforge.plugins.merlin.rest.MerlinPagesRest","org.projectforge.plugins.todo.ToDoDao","org.projectforge.plugins.todo.ToDoListPage","org.projectforge.plugins.todo.rest.ToDoPagesRest","org.projectforge.rest.AttachmentPageRest","org.projectforge.rest.BookPagesRest","org.projectforge.rest.hr.LeaveAccountEntryPagesRest","org.projectforge.rest.importer.AbstractImportPageRest","org.projectforge.rest.my2fa.My2FASetupPageRest","org.projectforge.security.webauthn.WebAuthnEntryDO","org.projectforge.ui.UIAgGridColumnDef","org.projectforge.ui.UIAttachmentList","org.projectforge.web.gantt.GanttChartListPage"],"usedInFiles":[]}, {"i18nKey":"createdBy","bundleName":"I18nResources","translation":"created by","translationDE":"angelegt von","usedInClasses":["org.projectforge.framework.persistence.jpa.impl.BaseDaoJpaAdapter","org.projectforge.rest.AttachmentPageRest","org.projectforge.ui.UIAttachmentList"],"usedInFiles":[]}, {"i18nKey":"currencyConverter.percentage.help","bundleName":"I18nResources","translation":"You can enter amounts as well as percent values (e. g. 10%).","translationDE":"Es können sowohl Beträge als auch Prozentzahlen (z. B. 10%) eingegeben werden.","usedInClasses":["org.projectforge.web.fibu.RechnungCostEditTablePanel"],"usedInFiles":[]}, {"i18nKey":"currencyFormat","bundleName":"I18nResources","translation":"{0,number,,##0.00}","translationDE":"{0,number,,##0.00}","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"datatable.no-records-found","bundleName":"I18nResources","translation":"No records found.","translationDE":"Keine Einträge gefunden.","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"date","bundleName":"I18nResources","translation":"date","translationDE":"Datum","usedInClasses":["org.projectforge.business.book.BookDO","org.projectforge.business.fibu.OrderExport","org.projectforge.business.fibu.datev.EmployeeSalaryExportDao","org.projectforge.business.fibu.kost.KostZuweisungExport","org.projectforge.business.orga.ContractDO","org.projectforge.business.orga.ContractDao","org.projectforge.business.orga.PostausgangDO","org.projectforge.business.orga.PosteingangDO","org.projectforge.business.scripting.ScriptParameterType","org.projectforge.business.vacation.model.LeaveAccountEntryDO","org.projectforge.business.vacation.repository.LeaveAccountEntryDao","org.projectforge.framework.persistence.database.json.DatabaseWriter","org.projectforge.plugins.banking.BankAccountRecordPagesRest","org.projectforge.plugins.liquidityplanning.LiquidityForecastCashFlow","org.projectforge.rest.VacationAccountPageRest","org.projectforge.rest.hr.LeaveAccountEntryPagesRest","org.projectforge.rest.orga.ContractPagesRest","org.projectforge.web.fibu.AccountingRecordEditForm","org.projectforge.web.fibu.DatevImportStoragePanel","org.projectforge.web.wicket.I18nParamMap","org.projectforge.web.wicket.WebConstants","org.projectforge.web.wicket.components.DateTimePanel"],"usedInFiles":["./plugins/org.projectforge.plugins.datatransfer/src/main/resources/mail/dataTransferMail.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/components/DateTimePanel.html"]}, + {"i18nKey":"date","bundleName":"I18nResources","translation":"date","translationDE":"Datum","usedInClasses":["org.projectforge.business.book.BookDO","org.projectforge.business.fibu.OrderExport","org.projectforge.business.fibu.datev.EmployeeSalaryExportDao","org.projectforge.business.fibu.kost.KostZuweisungExport","org.projectforge.business.orga.ContractDO","org.projectforge.business.orga.ContractDao","org.projectforge.business.orga.PostausgangDO","org.projectforge.business.orga.PosteingangDO","org.projectforge.business.poll.PollDO","org.projectforge.business.scripting.ScriptParameterType","org.projectforge.business.vacation.model.LeaveAccountEntryDO","org.projectforge.business.vacation.repository.LeaveAccountEntryDao","org.projectforge.framework.persistence.database.json.DatabaseWriter","org.projectforge.plugins.banking.BankAccountRecordPagesRest","org.projectforge.plugins.liquidityplanning.LiquidityForecastCashFlow","org.projectforge.rest.VacationAccountPageRest","org.projectforge.rest.hr.LeaveAccountEntryPagesRest","org.projectforge.rest.orga.ContractPagesRest","org.projectforge.rest.poll.PollPageRest","org.projectforge.web.fibu.AccountingRecordEditForm","org.projectforge.web.fibu.DatevImportStoragePanel","org.projectforge.web.wicket.I18nParamMap","org.projectforge.web.wicket.WebConstants","org.projectforge.web.wicket.components.DateTimePanel"],"usedInFiles":["./plugins/org.projectforge.plugins.datatransfer/src/main/resources/mail/dataTransferMail.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/components/DateTimePanel.html"]}, {"i18nKey":"date.begin","bundleName":"I18nResources","translation":"Start date","translationDE":"Beginndatum","usedInClasses":["org.projectforge.rest.core.AbstractPagesRest","org.projectforge.rest.scripting.AbstractScriptExecutePageRest"],"usedInFiles":[]}, {"i18nKey":"date.end","bundleName":"I18nResources","translation":"End date","translationDE":"Endedatum","usedInClasses":["org.projectforge.rest.core.AbstractPagesRest","org.projectforge.rest.scripting.AbstractScriptExecutePageRest"],"usedInFiles":[]}, {"i18nKey":"date.from","bundleName":"I18nResources","translation":"from","translationDE":"von","usedInClasses":[],"usedInFiles":[]}, @@ -761,11 +761,11 @@ {"i18nKey":"dateFormat.xls","bundleName":"I18nResources","translation":"Excel date format","translationDE":"Exceldatumsformat","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, {"i18nKey":"day","bundleName":"I18nResources","translation":"day(s)","translationDE":"Tag(e)","usedInClasses":["org.projectforge.business.fibu.MonthlyEmployeeReport","org.projectforge.business.gantt.GanttXUnit","org.projectforge.business.teamcal.event.RecurrenceFrequencyModeTwo","org.projectforge.framework.calendar.DayMonthYearHolder"],"usedInFiles":[]}, {"i18nKey":"days","bundleName":"I18nResources","translation":"days","translationDE":"Tage","usedInClasses":["org.projectforge.framework.calendar.WeekHolder","org.projectforge.framework.i18n.Duration","org.projectforge.framework.i18n.TimeAgo","org.projectforge.plugins.liquidityplanning.LiquidityForecastForm","org.projectforge.rest.calendar.FullCalendarEvent","org.projectforge.statistics.TimesheetDisciplineChartBuilder","org.projectforge.web.fibu.AbstractRechnungEditForm","org.projectforge.web.task.TaskEditForm"],"usedInFiles":[]}, - {"i18nKey":"deadline","bundleName":"I18nResources","translation":"Deadline","translationDE":"Frist","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"deadline","bundleName":"I18nResources","translation":"Deadline","translationDE":"Frist","usedInClasses":["org.projectforge.business.poll.PollDO","org.projectforge.rest.poll.PollInfoPageRest","org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, {"i18nKey":"default","bundleName":"I18nResources","translation":"Default","translationDE":"Standard","usedInClasses":["org.apache.batik.util.XMLConstants","org.projectforge.business.teamcal.filter.TeamCalCalendarFilter","org.projectforge.caldav.model.AddressBook","org.projectforge.rest.calendar.CalendarSettingsPageRest","org.projectforge.web.fibu.RechnungEditPage"],"usedInFiles":[]}, {"i18nKey":"delete","bundleName":"I18nResources","translation":"Delete","translationDE":"Löschen","usedInClasses":["org.projectforge.favorites.Favorites","org.projectforge.framework.access.AccessEntryDO","org.projectforge.framework.access.OperationType","org.projectforge.framework.jcr.AttachmentsEventType","org.projectforge.model.rest.RestPaths","org.projectforge.plugins.datatransfer.restPublic.DataTransferPublicServicesRest","org.projectforge.plugins.merlin.rest.MerlinVariablePageRest","org.projectforge.rest.AddressPagesRest","org.projectforge.rest.AttachmentPageRest","org.projectforge.rest.AttachmentsServicesRest","org.projectforge.rest.TimesheetFavoritesRest","org.projectforge.rest.my2fa.WebAuthnEntryPageRest","org.projectforge.rest.task.TaskFavoritesRest","org.projectforge.ui.LayoutUtils","org.projectforge.ui.UIAttachmentList","org.projectforge.web.fibu.AbstractRechnungEditForm","org.projectforge.web.fibu.AuftragEditForm","org.projectforge.web.fibu.PaymentSchedulePanel","org.projectforge.web.gantt.GanttChartEditTreeTablePanel","org.projectforge.web.humanresources.HRPlanningEditForm","org.projectforge.web.teamcal.dialog.TeamCalFilterDialog","org.projectforge.web.wicket.AbstractEditForm","org.projectforge.web.wicket.autocompletion.PFAutoCompleteTextField","org.projectforge.web.wicket.flowlayout.FileUploadPanel","org.projectforge.web.wicket.flowlayout.ImageUploadPanel"],"usedInFiles":["./projectforge-wicket/src/main/java/org/projectforge/web/fibu/PaymentSchedulePanel.html"]}, {"i18nKey":"deleted","bundleName":"I18nResources","translation":"deleted","translationDE":"gelöscht","usedInClasses":["org.projectforge.business.address.AddressDao","org.projectforge.business.fibu.EmployeeDao","org.projectforge.business.fibu.ProjektFilter","org.projectforge.business.fibu.kost.KostZuweisungExport","org.projectforge.business.humanresources.HRPlanningEntryDao","org.projectforge.business.teamcal.event.CalEventDao","org.projectforge.business.teamcal.event.TeamEventDao","org.projectforge.business.teamcal.event.TeamEventServiceImpl","org.projectforge.business.timesheet.TimesheetDao","org.projectforge.business.user.UserDao","org.projectforge.business.vacation.repository.VacationDao","org.projectforge.export.DOListExcelExporter","org.projectforge.flyway.dbmigration.V7_0_0_15__AuthenticationToken","org.projectforge.flyway.dbmigration.V7_0_0_6__MigrateEmployeeAndCarryVacationDays","org.projectforge.flyway.dbmigration.V7_4_1_3__ReleaseUserPassword","org.projectforge.framework.access.AccessDao","org.projectforge.framework.persistence.api.BaseDao","org.projectforge.framework.persistence.api.MagicFilterProcessor","org.projectforge.framework.persistence.api.QueryFilter","org.projectforge.framework.persistence.entities.AbstractBaseDO","org.projectforge.plugins.todo.ToDoDao","org.projectforge.renderer.custom.MicromataFormatter","org.projectforge.rest.core.AbstractPagesRest","org.projectforge.rest.importer.AbstractImportPageRest","org.projectforge.rest.task.TaskServicesRest","org.projectforge.ui.filter.LayoutListFilterUtils","org.projectforge.web.rest.TaskDaoRest","org.projectforge.web.rest.TimesheetDaoRest","org.projectforge.web.task.TaskTreeForm","org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]}, - {"i18nKey":"description","bundleName":"I18nResources","translation":"Description","translationDE":"Beschreibung","usedInClasses":["org.projectforge.business.fibu.EmployeeDO","org.projectforge.business.fibu.KontoDO","org.projectforge.business.fibu.KundeDO","org.projectforge.business.fibu.ProjektDO","org.projectforge.business.fibu.datev.EmployeeSalaryExportDao","org.projectforge.business.fibu.kost.Kost1DO","org.projectforge.business.fibu.kost.Kost2ArtDO","org.projectforge.business.fibu.kost.Kost2DO","org.projectforge.business.fibu.kost.KostZuweisungExport","org.projectforge.business.gantt.GanttChartDao","org.projectforge.business.ldap.GroupDOConverter","org.projectforge.business.ldap.LdapGroupDao","org.projectforge.business.ldap.LdapOrganizationalUnitDao","org.projectforge.business.ldap.LdapPersonDao","org.projectforge.business.ldap.PFUserDOConverter","org.projectforge.business.scripting.ScriptDO","org.projectforge.business.task.TaskDO","org.projectforge.business.timesheet.TimesheetDO","org.projectforge.business.vacation.model.LeaveAccountEntryDO","org.projectforge.framework.access.GroupTaskAccessDO","org.projectforge.framework.jcr.Attachment","org.projectforge.framework.jobs.JobHandler","org.projectforge.framework.persistence.user.entities.GroupDO","org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.plugins.banking.BankAccountDO","org.projectforge.plugins.banking.BankAccountPagesRest","org.projectforge.plugins.datatransfer.rest.DataTransferAreaPagesRest","org.projectforge.plugins.datatransfer.rest.DataTransferAuditPageRest","org.projectforge.plugins.datatransfer.rest.DataTransferPageRest","org.projectforge.plugins.ihk.IHKExporter","org.projectforge.plugins.merlin.MerlinTemplateDO","org.projectforge.plugins.merlin.MerlinVariableBase","org.projectforge.plugins.merlin.rest.MerlinPagesRest","org.projectforge.plugins.todo.ToDoDO","org.projectforge.plugins.todo.ToDoEditForm","org.projectforge.plugins.todo.ToDoListPage","org.projectforge.plugins.todo.rest.ToDoPagesRest","org.projectforge.renderer.custom.MicromataFormatter","org.projectforge.rest.AddressBookPagesRest","org.projectforge.rest.GroupAccessPagesRest","org.projectforge.rest.GroupPagesRest","org.projectforge.rest.TeamCalPagesRest","org.projectforge.rest.TimesheetMultiSelectedPageRest","org.projectforge.rest.TimesheetPagesRest","org.projectforge.rest.UserPagesRest","org.projectforge.rest.VacationAccountPageRest","org.projectforge.rest.calendar.TimesheetEventsProvider","org.projectforge.rest.fibu.CustomerPagesRest","org.projectforge.rest.fibu.KontoPagesRest","org.projectforge.rest.fibu.ProjectMultiSelectedPageRest","org.projectforge.rest.fibu.ProjectPagesRest","org.projectforge.rest.fibu.kost.Kost1PagesRest","org.projectforge.rest.fibu.kost.Kost2ArtPagesRest","org.projectforge.rest.fibu.kost.Kost2PagesRest","org.projectforge.rest.hr.HRPlanningListPagesRest","org.projectforge.rest.hr.LeaveAccountEntryPagesRest","org.projectforge.rest.scripting.AbstractScriptExecutePageRest","org.projectforge.rest.scripting.MyScriptPagesRest","org.projectforge.rest.scripting.ScriptPagesRest","org.projectforge.rest.task.TaskPagesRest","org.projectforge.ui.UIAttachmentList","org.projectforge.web.access.AccessEditForm","org.projectforge.web.access.AccessListPage","org.projectforge.web.admin.ConfigurationEditForm","org.projectforge.web.admin.ConfigurationListPage","org.projectforge.web.calendar.TimesheetEventsProvider","org.projectforge.web.fibu.CustomerEditForm","org.projectforge.web.fibu.CustomerListPage","org.projectforge.web.fibu.KontoEditForm","org.projectforge.web.fibu.KontoListPage","org.projectforge.web.fibu.KontoSelectPanel","org.projectforge.web.fibu.Kost1EditForm","org.projectforge.web.fibu.Kost1ListPage","org.projectforge.web.fibu.Kost2ArtEditForm","org.projectforge.web.fibu.Kost2ArtListPage","org.projectforge.web.fibu.Kost2EditForm","org.projectforge.web.fibu.Kost2ListPage","org.projectforge.web.fibu.ProjektEditForm","org.projectforge.web.fibu.ProjektListPage","org.projectforge.web.fibu.ReportObjectivesPanel","org.projectforge.web.humanresources.HRPlanningEditForm","org.projectforge.web.humanresources.HRPlanningListPage","org.projectforge.web.task.TaskEditForm","org.projectforge.web.teamcal.admin.TeamCalEditForm","org.projectforge.web.teamcal.admin.TeamCalListPage","org.projectforge.web.timesheet.TimesheetEditForm","org.projectforge.web.timesheet.TimesheetEditPage","org.projectforge.web.timesheet.TimesheetListPage","org.projectforge.web.user.GroupEditForm","org.projectforge.web.user.GroupListPage","org.projectforge.web.user.UserEditForm","org.projectforge.web.user.UserListPage","org.projectforge.web.wicket.ErrorForm","org.projectforge.web.wicket.FeedbackForm"],"usedInFiles":["./plugins/org.projectforge.plugins.datatransfer/src/main/resources/mail/dataTransferMail.html","./projectforge-business/src/main/resources/htmlTemplates/teamEventResponse.html","./projectforge-business/src/main/resources/mail/teamEventEmail.html","./projectforge-business/src/main/resources/mail/todoChangeNotification.html","./projectforge-wicket/src/main/java/org/projectforge/web/fibu/ReportObjectivesPanel.html"]}, + {"i18nKey":"description","bundleName":"I18nResources","translation":"Description","translationDE":"Beschreibung","usedInClasses":["org.projectforge.business.fibu.EmployeeDO","org.projectforge.business.fibu.KontoDO","org.projectforge.business.fibu.KundeDO","org.projectforge.business.fibu.ProjektDO","org.projectforge.business.fibu.datev.EmployeeSalaryExportDao","org.projectforge.business.fibu.kost.Kost1DO","org.projectforge.business.fibu.kost.Kost2ArtDO","org.projectforge.business.fibu.kost.Kost2DO","org.projectforge.business.fibu.kost.KostZuweisungExport","org.projectforge.business.gantt.GanttChartDao","org.projectforge.business.ldap.GroupDOConverter","org.projectforge.business.ldap.LdapGroupDao","org.projectforge.business.ldap.LdapOrganizationalUnitDao","org.projectforge.business.ldap.LdapPersonDao","org.projectforge.business.ldap.PFUserDOConverter","org.projectforge.business.poll.PollDO","org.projectforge.business.scripting.ScriptDO","org.projectforge.business.task.TaskDO","org.projectforge.business.timesheet.TimesheetDO","org.projectforge.business.vacation.model.LeaveAccountEntryDO","org.projectforge.framework.access.GroupTaskAccessDO","org.projectforge.framework.jcr.Attachment","org.projectforge.framework.jobs.JobHandler","org.projectforge.framework.persistence.user.entities.GroupDO","org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.plugins.banking.BankAccountDO","org.projectforge.plugins.banking.BankAccountPagesRest","org.projectforge.plugins.datatransfer.rest.DataTransferAreaPagesRest","org.projectforge.plugins.datatransfer.rest.DataTransferAuditPageRest","org.projectforge.plugins.datatransfer.rest.DataTransferPageRest","org.projectforge.plugins.ihk.IHKExporter","org.projectforge.plugins.merlin.MerlinTemplateDO","org.projectforge.plugins.merlin.MerlinVariableBase","org.projectforge.plugins.merlin.rest.MerlinPagesRest","org.projectforge.plugins.todo.ToDoDO","org.projectforge.plugins.todo.ToDoEditForm","org.projectforge.plugins.todo.ToDoListPage","org.projectforge.plugins.todo.rest.ToDoPagesRest","org.projectforge.renderer.custom.MicromataFormatter","org.projectforge.rest.AddressBookPagesRest","org.projectforge.rest.GroupAccessPagesRest","org.projectforge.rest.GroupPagesRest","org.projectforge.rest.TeamCalPagesRest","org.projectforge.rest.TimesheetMultiSelectedPageRest","org.projectforge.rest.TimesheetPagesRest","org.projectforge.rest.UserPagesRest","org.projectforge.rest.VacationAccountPageRest","org.projectforge.rest.calendar.TimesheetEventsProvider","org.projectforge.rest.fibu.CustomerPagesRest","org.projectforge.rest.fibu.KontoPagesRest","org.projectforge.rest.fibu.ProjectMultiSelectedPageRest","org.projectforge.rest.fibu.ProjectPagesRest","org.projectforge.rest.fibu.kost.Kost1PagesRest","org.projectforge.rest.fibu.kost.Kost2ArtPagesRest","org.projectforge.rest.fibu.kost.Kost2PagesRest","org.projectforge.rest.hr.HRPlanningListPagesRest","org.projectforge.rest.hr.LeaveAccountEntryPagesRest","org.projectforge.rest.poll.PollInfoPageRest","org.projectforge.rest.poll.PollPageRest","org.projectforge.rest.scripting.AbstractScriptExecutePageRest","org.projectforge.rest.scripting.MyScriptPagesRest","org.projectforge.rest.scripting.ScriptPagesRest","org.projectforge.rest.task.TaskPagesRest","org.projectforge.ui.UIAttachmentList","org.projectforge.web.access.AccessEditForm","org.projectforge.web.access.AccessListPage","org.projectforge.web.admin.ConfigurationEditForm","org.projectforge.web.admin.ConfigurationListPage","org.projectforge.web.calendar.TimesheetEventsProvider","org.projectforge.web.fibu.CustomerEditForm","org.projectforge.web.fibu.CustomerListPage","org.projectforge.web.fibu.KontoEditForm","org.projectforge.web.fibu.KontoListPage","org.projectforge.web.fibu.KontoSelectPanel","org.projectforge.web.fibu.Kost1EditForm","org.projectforge.web.fibu.Kost1ListPage","org.projectforge.web.fibu.Kost2ArtEditForm","org.projectforge.web.fibu.Kost2ArtListPage","org.projectforge.web.fibu.Kost2EditForm","org.projectforge.web.fibu.Kost2ListPage","org.projectforge.web.fibu.ProjektEditForm","org.projectforge.web.fibu.ProjektListPage","org.projectforge.web.fibu.ReportObjectivesPanel","org.projectforge.web.humanresources.HRPlanningEditForm","org.projectforge.web.humanresources.HRPlanningListPage","org.projectforge.web.task.TaskEditForm","org.projectforge.web.teamcal.admin.TeamCalEditForm","org.projectforge.web.teamcal.admin.TeamCalListPage","org.projectforge.web.timesheet.TimesheetEditForm","org.projectforge.web.timesheet.TimesheetEditPage","org.projectforge.web.timesheet.TimesheetListPage","org.projectforge.web.user.GroupEditForm","org.projectforge.web.user.GroupListPage","org.projectforge.web.user.UserEditForm","org.projectforge.web.user.UserListPage","org.projectforge.web.wicket.ErrorForm","org.projectforge.web.wicket.FeedbackForm"],"usedInFiles":["./plugins/org.projectforge.plugins.datatransfer/src/main/resources/mail/dataTransferMail.html","./projectforge-business/src/main/resources/htmlTemplates/teamEventResponse.html","./projectforge-business/src/main/resources/mail/teamEventEmail.html","./projectforge-business/src/main/resources/mail/todoChangeNotification.html","./projectforge-wicket/src/main/java/org/projectforge/web/fibu/ReportObjectivesPanel.html"]}, {"i18nKey":"deselectAll","bundleName":"I18nResources","translation":"Deselect all","translationDE":"Alle abwählen","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"dialog.title.error","bundleName":"I18nResources","translation":"An error occured!","translationDE":"Es ist ein Fehler aufgetreten!","usedInClasses":["org.projectforge.web.dialog.ModalMessageDialog"],"usedInFiles":[]}, {"i18nKey":"dialog.title.information","bundleName":"I18nResources","translation":"Information","translationDE":"Information","usedInClasses":["org.projectforge.web.dialog.ModalMessageDialog"],"usedInFiles":[]}, @@ -812,7 +812,7 @@ {"i18nKey":"exception.pleaseContactDeveloperTeam","bundleName":"I18nResources","translation":"Internal error, please contact the developer team (the message id in error log: #{0}).","translationDE":"Interner Fehler, bitte die Entwickler:innen informieren. Die Fehlerkennung in den Fehlerprotokollen lautet #{0}.","usedInClasses":["org.projectforge.common.i18n.UserException"],"usedInFiles":[]}, {"i18nKey":"exception.scriptError","bundleName":"I18nResources","translation":"Exception while script execution thrown: {0}","translationDE":"Fehler bei der Script-Ausführung: {0}","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"execute","bundleName":"I18nResources","translation":"Execute","translationDE":"Ausführen","usedInClasses":["org.projectforge.plugins.liquidityplanning.LiquidityForecastForm","org.projectforge.plugins.merlin.rest.MerlinExecutionPageRest","org.projectforge.rest.multiselect.AbstractMultiSelectedPage","org.projectforge.rest.scripting.AbstractScriptExecutePageRest","org.projectforge.rest.scripting.ScriptPagesRest","org.projectforge.rest.task.TaskWizardPageRest","org.projectforge.web.admin.GroovyConsoleForm","org.projectforge.web.admin.SqlConsoleForm"],"usedInFiles":[]}, - {"i18nKey":"export","bundleName":"I18nResources","translation":"Export","translationDE":"Export","usedInClasses":["org.projectforge.export.DOListExcelExporter","org.projectforge.plugins.eed.wicket.ExportDataForm","org.projectforge.rest.AddressPagesRest","org.projectforge.rest.TeamCalPagesRest","org.projectforge.rest.VacationAccountPageRest","org.projectforge.rest.VacationExportPageRest","org.projectforge.rest.VacationPagesRest","org.projectforge.web.address.AddressListPage","org.projectforge.web.fibu.EingangsrechnungListForm","org.projectforge.web.gantt.GanttChartEditForm","org.projectforge.web.timesheet.TimesheetListPage"],"usedInFiles":[]}, + {"i18nKey":"export","bundleName":"I18nResources","translation":"Export","translationDE":"Export","usedInClasses":["org.projectforge.export.DOListExcelExporter","org.projectforge.plugins.eed.wicket.ExportDataForm","org.projectforge.rest.AddressPagesRest","org.projectforge.rest.TeamCalPagesRest","org.projectforge.rest.VacationAccountPageRest","org.projectforge.rest.VacationExportPageRest","org.projectforge.rest.VacationPagesRest","org.projectforge.rest.poll.PollPageRest","org.projectforge.web.address.AddressListPage","org.projectforge.web.fibu.EingangsrechnungListForm","org.projectforge.web.gantt.GanttChartEditForm","org.projectforge.web.timesheet.TimesheetListPage"],"usedInFiles":[]}, {"i18nKey":"exportAsPdf","bundleName":"I18nResources","translation":"Pdf export","translationDE":"Pdf-Export","usedInClasses":["org.projectforge.web.fibu.MonthlyEmployeeReportPage","org.projectforge.web.timesheet.TimesheetListPage"],"usedInFiles":[]}, {"i18nKey":"exportAsXls","bundleName":"I18nResources","translation":"Excel export","translationDE":"Excel-Export","usedInClasses":["org.projectforge.plugins.skillmatrix.SkillEntryPagesRest","org.projectforge.rest.VacationExportPageRest","org.projectforge.rest.core.AbstractPagesRest","org.projectforge.web.fibu.AuftragListPage","org.projectforge.web.fibu.Kost1ListPage","org.projectforge.web.fibu.Kost2ListPage","org.projectforge.web.timesheet.TimesheetListPage","org.projectforge.web.wicket.AbstractListPage"],"usedInFiles":[]}, {"i18nKey":"favorite","bundleName":"I18nResources","translation":"Favorite","translationDE":"Favorit","usedInClasses":["org.projectforge.rest.AddressPagesRest","org.projectforge.rest.AddressViewPageRest"],"usedInFiles":[]}, @@ -1416,7 +1416,7 @@ {"i18nKey":"hr.planning.weekend","bundleName":"I18nResources","translation":"Week-end","translationDE":"Wochenende","usedInClasses":["org.projectforge.business.humanresources.HRPlanningEntryDO","org.projectforge.web.humanresources.HRPlanningListPage"],"usedInFiles":[]}, {"i18nKey":"hr.planning.workdays","bundleName":"I18nResources","translation":"Workdays","translationDE":"Arbeitstage","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"ibanvalidator.wronglength.de","bundleName":"I18nResources","translation":"The field ''${label}'' starts with ''DE'', but a german IBAN must have 22 characters.","translationDE":"Das Feld ''${label}'' beginnt mit ''DE''. Eine deutsche IBAN muss jedoch aus 22 Zeichen bestehen.","usedInClasses":["org.projectforge.web.common.IbanValidator"],"usedInFiles":[]}, - {"i18nKey":"id","bundleName":"I18nResources","translation":"Id","translationDE":"Id","usedInClasses":["org.apache.batik.util.XMLConstants","org.projectforge.business.address.AddressExport","org.projectforge.business.address.PersonalAddressDao","org.projectforge.business.book.BookDao","org.projectforge.business.fibu.AuftragDao","org.projectforge.business.fibu.EingangsrechnungDO","org.projectforge.business.fibu.EingangsrechnungsPositionDO","org.projectforge.business.fibu.EmployeeDO","org.projectforge.business.fibu.EmployeeSalaryDao","org.projectforge.business.fibu.InvoiceService","org.projectforge.business.fibu.RechnungDO","org.projectforge.business.fibu.RechnungDao","org.projectforge.business.fibu.RechnungsPositionDO","org.projectforge.business.fibu.kost.Kost1Dao","org.projectforge.business.fibu.kost.Kost2ArtDao","org.projectforge.business.fibu.kost.Kost2Dao","org.projectforge.business.fibu.kost.KostZuweisungDO","org.projectforge.business.gantt.GanttChart","org.projectforge.business.gantt.GanttChartDao","org.projectforge.business.gantt.GanttTaskImpl","org.projectforge.business.humanresources.HRPlanningDO","org.projectforge.business.humanresources.HRPlanningDao","org.projectforge.business.humanresources.HRPlanningEntryDO","org.projectforge.business.orga.ContractDao","org.projectforge.business.task.TaskDao","org.projectforge.business.task.TaskNode","org.projectforge.business.task.formatter.WicketTaskFormatter","org.projectforge.business.timesheet.TimesheetDao","org.projectforge.business.timesheet.TimesheetExport","org.projectforge.business.user.GroupDao","org.projectforge.business.user.UserDao","org.projectforge.business.user.UserPrefDao","org.projectforge.excel.ExcelUtils","org.projectforge.framework.ToStringUtil","org.projectforge.framework.access.AccessDao","org.projectforge.framework.access.AccessEntryDO","org.projectforge.framework.access.GroupTaskAccessDO","org.projectforge.framework.jobs.AbstractJob","org.projectforge.framework.json.HibernateProxySerializer","org.projectforge.framework.persistence.api.BaseDao","org.projectforge.framework.persistence.database.HistoryMigrateService","org.projectforge.framework.persistence.database.ReindexerRegistry","org.projectforge.framework.persistence.database.ReindexerStrategy","org.projectforge.framework.persistence.entities.DefaultBaseDO","org.projectforge.framework.persistence.history.HibernateSearchDependentObjectsReindexer","org.projectforge.framework.persistence.history.HibernateSearchReindexer","org.projectforge.framework.persistence.jpa.impl.BaseDaoJpaAdapter","org.projectforge.framework.persistence.jpa.impl.LuceneServiceImpl","org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.framework.persistence.user.entities.UserPrefEntryDO","org.projectforge.framework.persistence.user.entities.UserRightDO","org.projectforge.framework.persistence.xstream.ProxyIdRefMarshaller","org.projectforge.menu.builder.FavoritesMenuReaderWriter","org.projectforge.plugins.banking.BankingServicesRest","org.projectforge.plugins.datatransfer.DataTransferAreaDO","org.projectforge.plugins.datatransfer.rest.DataTransferAuditPageRest","org.projectforge.plugins.datatransfer.rest.DataTransferPageRest","org.projectforge.plugins.datatransfer.restPublic.DataTransferPublicAttachmentPageRest","org.projectforge.plugins.datatransfer.restPublic.DataTransferPublicPageRest","org.projectforge.plugins.datatransfer.restPublic.DataTransferPublicServicesRest","org.projectforge.plugins.eed.excelimport.EmployeeBillingExcelImporter","org.projectforge.plugins.eed.wicket.EmployeeBillingImportStoragePanel","org.projectforge.plugins.eed.wicket.EmployeeListEditPage","org.projectforge.plugins.ihk.IHKExporter","org.projectforge.plugins.memo.MemoDO","org.projectforge.plugins.merlin.MerlinTemplateDO","org.projectforge.plugins.merlin.rest.MerlinExecutionPageRest","org.projectforge.plugins.merlin.rest.MerlinVariablePageRest","org.projectforge.plugins.skillmatrix.SkillEntryDO","org.projectforge.rest.AddressImageServicesRest","org.projectforge.rest.AddressServicesRest","org.projectforge.rest.AddressViewPageRest","org.projectforge.rest.AttachmentPageRest","org.projectforge.rest.AttachmentsServicesRest","org.projectforge.rest.TimesheetFavoritesRest","org.projectforge.rest.TimesheetMultiSelectedPageRest","org.projectforge.rest.TimesheetPagesRest","org.projectforge.rest.VacationAccountPageRest","org.projectforge.rest.admin.LogViewerPageRest","org.projectforge.rest.calendar.CalEventPagesRest","org.projectforge.rest.calendar.CalendarFilterServicesRest","org.projectforge.rest.calendar.CalendarSettingsPageRest","org.projectforge.rest.calendar.TeamEventPagesRest","org.projectforge.rest.config.IdObjectDeserializer","org.projectforge.rest.config.JacksonConfiguration","org.projectforge.rest.core.AbstractPagesRest","org.projectforge.rest.dvelop.DvelopClient","org.projectforge.rest.fibu.kost.Kost2ArtPagesRest","org.projectforge.rest.importer.AbstractImportPageRest","org.projectforge.rest.json.UISelectTypeSerializer","org.projectforge.rest.my2fa.My2FAServicesRest","org.projectforge.rest.my2fa.My2FASetupPageRest","org.projectforge.rest.my2fa.WebAuthnEntryPageRest","org.projectforge.rest.orga.VisitorbookPagesRest","org.projectforge.rest.scripting.MyScriptExecutePageRest","org.projectforge.rest.scripting.ScriptExecutePageRest","org.projectforge.rest.scripting.ScriptPagesRest","org.projectforge.rest.task.TaskFavoritesRest","org.projectforge.rest.task.TaskServicesRest","org.projectforge.security.dto.WebAuthnPublicKeyCredentialCreationOptions","org.projectforge.security.webauthn.WebAuthnEntryDao","org.projectforge.ui.UISelect","org.projectforge.web.OrphanedLinkFilter","org.projectforge.web.fibu.CustomerEditForm","org.projectforge.web.fibu.Kost2ArtEditForm","org.projectforge.web.fibu.Kost2ArtListPage","org.projectforge.web.fibu.NewCustomerSelectPanel","org.projectforge.web.fibu.NewProjektSelectPanel","org.projectforge.web.gantt.GanttTreeTableNode","org.projectforge.web.orga.VisitorbookListPage","org.projectforge.web.user.NewGroupSelectPanel","org.projectforge.web.wicket.AbstractEditPage","org.projectforge.web.wicket.components.TabPanel"],"usedInFiles":["./projectforge-rest/src/main/kotlin/org/projectforge/rest/json/Deserializers.kt"]}, + {"i18nKey":"id","bundleName":"I18nResources","translation":"Id","translationDE":"Id","usedInClasses":["org.apache.batik.util.XMLConstants","org.projectforge.business.address.AddressExport","org.projectforge.business.address.PersonalAddressDao","org.projectforge.business.book.BookDao","org.projectforge.business.fibu.AuftragDao","org.projectforge.business.fibu.EingangsrechnungDO","org.projectforge.business.fibu.EingangsrechnungsPositionDO","org.projectforge.business.fibu.EmployeeDO","org.projectforge.business.fibu.EmployeeSalaryDao","org.projectforge.business.fibu.InvoiceService","org.projectforge.business.fibu.RechnungDO","org.projectforge.business.fibu.RechnungDao","org.projectforge.business.fibu.RechnungsPositionDO","org.projectforge.business.fibu.kost.Kost1Dao","org.projectforge.business.fibu.kost.Kost2ArtDao","org.projectforge.business.fibu.kost.Kost2Dao","org.projectforge.business.fibu.kost.KostZuweisungDO","org.projectforge.business.gantt.GanttChart","org.projectforge.business.gantt.GanttChartDao","org.projectforge.business.gantt.GanttTaskImpl","org.projectforge.business.humanresources.HRPlanningDO","org.projectforge.business.humanresources.HRPlanningDao","org.projectforge.business.humanresources.HRPlanningEntryDO","org.projectforge.business.orga.ContractDao","org.projectforge.business.task.TaskDao","org.projectforge.business.task.TaskNode","org.projectforge.business.task.formatter.WicketTaskFormatter","org.projectforge.business.timesheet.TimesheetDao","org.projectforge.business.timesheet.TimesheetExport","org.projectforge.business.user.GroupDao","org.projectforge.business.user.UserDao","org.projectforge.business.user.UserPrefDao","org.projectforge.excel.ExcelUtils","org.projectforge.framework.ToStringUtil","org.projectforge.framework.access.AccessDao","org.projectforge.framework.access.AccessEntryDO","org.projectforge.framework.access.GroupTaskAccessDO","org.projectforge.framework.jobs.AbstractJob","org.projectforge.framework.json.HibernateProxySerializer","org.projectforge.framework.persistence.api.BaseDao","org.projectforge.framework.persistence.database.HistoryMigrateService","org.projectforge.framework.persistence.database.ReindexerRegistry","org.projectforge.framework.persistence.database.ReindexerStrategy","org.projectforge.framework.persistence.entities.DefaultBaseDO","org.projectforge.framework.persistence.history.HibernateSearchDependentObjectsReindexer","org.projectforge.framework.persistence.history.HibernateSearchReindexer","org.projectforge.framework.persistence.jpa.impl.BaseDaoJpaAdapter","org.projectforge.framework.persistence.jpa.impl.LuceneServiceImpl","org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.framework.persistence.user.entities.UserPrefEntryDO","org.projectforge.framework.persistence.user.entities.UserRightDO","org.projectforge.framework.persistence.xstream.ProxyIdRefMarshaller","org.projectforge.menu.builder.FavoritesMenuReaderWriter","org.projectforge.plugins.banking.BankingServicesRest","org.projectforge.plugins.datatransfer.DataTransferAreaDO","org.projectforge.plugins.datatransfer.rest.DataTransferAuditPageRest","org.projectforge.plugins.datatransfer.rest.DataTransferPageRest","org.projectforge.plugins.datatransfer.restPublic.DataTransferPublicAttachmentPageRest","org.projectforge.plugins.datatransfer.restPublic.DataTransferPublicPageRest","org.projectforge.plugins.datatransfer.restPublic.DataTransferPublicServicesRest","org.projectforge.plugins.eed.excelimport.EmployeeBillingExcelImporter","org.projectforge.plugins.eed.wicket.EmployeeBillingImportStoragePanel","org.projectforge.plugins.eed.wicket.EmployeeListEditPage","org.projectforge.plugins.ihk.IHKExporter","org.projectforge.plugins.memo.MemoDO","org.projectforge.plugins.merlin.MerlinTemplateDO","org.projectforge.plugins.merlin.rest.MerlinExecutionPageRest","org.projectforge.plugins.merlin.rest.MerlinVariablePageRest","org.projectforge.plugins.skillmatrix.SkillEntryDO","org.projectforge.rest.AddressImageServicesRest","org.projectforge.rest.AddressServicesRest","org.projectforge.rest.AddressViewPageRest","org.projectforge.rest.AttachmentPageRest","org.projectforge.rest.AttachmentsServicesRest","org.projectforge.rest.TimesheetFavoritesRest","org.projectforge.rest.TimesheetMultiSelectedPageRest","org.projectforge.rest.TimesheetPagesRest","org.projectforge.rest.VacationAccountPageRest","org.projectforge.rest.admin.LogViewerPageRest","org.projectforge.rest.calendar.CalEventPagesRest","org.projectforge.rest.calendar.CalendarFilterServicesRest","org.projectforge.rest.calendar.CalendarSettingsPageRest","org.projectforge.rest.calendar.TeamEventPagesRest","org.projectforge.rest.config.IdObjectDeserializer","org.projectforge.rest.config.JacksonConfiguration","org.projectforge.rest.core.AbstractPagesRest","org.projectforge.rest.dvelop.DvelopClient","org.projectforge.rest.fibu.kost.Kost2ArtPagesRest","org.projectforge.rest.importer.AbstractImportPageRest","org.projectforge.rest.json.UISelectTypeSerializer","org.projectforge.rest.my2fa.My2FAServicesRest","org.projectforge.rest.my2fa.My2FASetupPageRest","org.projectforge.rest.my2fa.WebAuthnEntryPageRest","org.projectforge.rest.orga.VisitorbookPagesRest","org.projectforge.rest.poll.PollPageRest","org.projectforge.rest.scripting.MyScriptExecutePageRest","org.projectforge.rest.scripting.ScriptExecutePageRest","org.projectforge.rest.scripting.ScriptPagesRest","org.projectforge.rest.task.TaskFavoritesRest","org.projectforge.rest.task.TaskServicesRest","org.projectforge.security.dto.WebAuthnPublicKeyCredentialCreationOptions","org.projectforge.security.webauthn.WebAuthnEntryDao","org.projectforge.ui.UISelect","org.projectforge.web.OrphanedLinkFilter","org.projectforge.web.fibu.CustomerEditForm","org.projectforge.web.fibu.Kost2ArtEditForm","org.projectforge.web.fibu.Kost2ArtListPage","org.projectforge.web.fibu.NewCustomerSelectPanel","org.projectforge.web.fibu.NewProjektSelectPanel","org.projectforge.web.gantt.GanttTreeTableNode","org.projectforge.web.orga.VisitorbookListPage","org.projectforge.web.user.NewGroupSelectPanel","org.projectforge.web.wicket.AbstractEditPage","org.projectforge.web.wicket.components.TabPanel"],"usedInFiles":["./projectforge-rest/src/main/kotlin/org/projectforge/rest/json/Deserializers.kt"]}, {"i18nKey":"imageFile","bundleName":"I18nResources","translation":"Image","translationDE":"Bild","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"import","bundleName":"I18nResources","translation":"Import","translationDE":"Importieren","usedInClasses":["org.projectforge.business.scripting.KotlinScriptExecutor","org.projectforge.rest.importer.AbstractImportPageRest","org.projectforge.web.admin.SetupImportForm","org.projectforge.web.fibu.ReportObjectivesForm","org.projectforge.web.teamcal.admin.TeamCalEditPage"],"usedInFiles":["./projectforge-wicket/src/main/java/org/projectforge/web/admin/SetupPage.html"]}, {"i18nKey":"import.confirmMessage","bundleName":"I18nResources","translation":"Would you like to import the selected entries now? This option isn't undoable.","translationDE":"Sollen nun alle ausgewählten Einträge importiert werden? Diese Aktion kann nicht rückgängig gemacht werden.","usedInClasses":["org.projectforge.rest.importer.AbstractImportPageRest"],"usedInFiles":[]}, @@ -1688,6 +1688,7 @@ {"i18nKey":"menu.phoneCall","bundleName":"I18nResources","translation":"Direct call","translationDE":"Direktwahl","usedInClasses":["org.projectforge.menu.builder.MenuItemDefId","org.projectforge.rest.AddressPagesRest"],"usedInFiles":[]}, {"i18nKey":"menu.pluginAdmin","bundleName":"I18nResources","translation":"Plugins","translationDE":"Plugins","usedInClasses":["org.projectforge.menu.builder.MenuItemDefId"],"usedInFiles":[]}, {"i18nKey":"menu.plugins.teamcal","bundleName":"I18nResources","translation":"List of calendars","translationDE":"Kalenderliste","usedInClasses":["org.projectforge.menu.builder.MenuItemDefId","org.projectforge.rest.calendar.CalendarFilterServicesRest"],"usedInFiles":[]}, + {"i18nKey":"menu.poll","bundleName":"I18nResources","translation":"Polls","translationDE":"Umfragen","usedInClasses":["org.projectforge.menu.builder.MenuItemDefId"],"usedInFiles":[]}, {"i18nKey":"menu.projectDocumentation","bundleName":"I18nResources","translation":"Project docs","translationDE":"Project docs","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"menu.projectmanagement","bundleName":"I18nResources","translation":"Project management","translationDE":"Projektmanagement","usedInClasses":["org.projectforge.menu.builder.MenuItemDefId"],"usedInFiles":[]}, {"i18nKey":"menu.reindexAllDatabaseEntries","bundleName":"I18nResources","translation":"Re-build whole search index","translationDE":"Suchindex voll indizieren","usedInClasses":["org.projectforge.rest.core.AbstractPagesRest","org.projectforge.web.wicket.AbstractReindexTopRightMenu"],"usedInFiles":[]}, @@ -1736,7 +1737,7 @@ {"i18nKey":"new","bundleName":"I18nResources","translation":"New","translationDE":"Neu","usedInClasses":["org.projectforge.rest.importer.AbstractImportPageRest","org.projectforge.web.wicket.AbstractListPage"],"usedInFiles":[]}, {"i18nKey":"next","bundleName":"I18nResources","translation":"Next","translationDE":"Weiter","usedInClasses":["org.projectforge.plugins.datatransfer.rest.DataTransferPersonalBoxPageRest","org.projectforge.ui.UIAgGrid","org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]}, {"i18nKey":"nickname","bundleName":"I18nResources","translation":"Nickname","translationDE":"Rufname","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.plugins.merlin.rest.MerlinExecutionPageRest","org.projectforge.web.user.UserEditForm","org.projectforge.web.user.UserListPage"],"usedInFiles":[]}, - {"i18nKey":"no","bundleName":"I18nResources","translation":"No","translationDE":"Nein","usedInClasses":["org.projectforge.business.vacation.service.VacationSendMailService","org.projectforge.rest.dto.Vacation"],"usedInFiles":["./projectforge-business/src/main/kotlin/org/projectforge/framework/i18n/I18n.kt"]}, + {"i18nKey":"no","bundleName":"I18nResources","translation":"No","translationDE":"Nein","usedInClasses":["org.projectforge.business.vacation.service.VacationSendMailService","org.projectforge.rest.dto.Vacation","org.projectforge.rest.poll.PollPageRest"],"usedInFiles":["./projectforge-business/src/main/kotlin/org/projectforge/framework/i18n/I18n.kt"]}, {"i18nKey":"notEnded","bundleName":"I18nResources","translation":"not ended","translationDE":"nicht beendet","usedInClasses":["org.projectforge.business.fibu.ProjektFilter","org.projectforge.business.fibu.kost.KostFilter","org.projectforge.web.fibu.Kost1ListForm","org.projectforge.web.fibu.Kost2ListForm","org.projectforge.web.fibu.ProjektListForm"],"usedInFiles":[]}, {"i18nKey":"notLoggedIn","bundleName":"I18nResources","translation":"Not logged in.","translationDE":"Nicht angemeldet","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"notVisible","bundleName":"I18nResources","translation":"not visible","translationDE":"nicht sichtbar","usedInClasses":["org.projectforge.business.utils.BaseFormatter"],"usedInFiles":[]}, @@ -2001,15 +2002,68 @@ {"i18nKey":"plugins.teamcal.selectTemplate","bundleName":"I18nResources","translation":"Choose filter template","translationDE":"Filterschablone auswählen","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"plugins.teamcal.subscription","bundleName":"I18nResources","translation":"Subscription","translationDE":"Abonnement","usedInClasses":["org.projectforge.rest.TeamCalPagesRest","org.projectforge.rest.calendar.CalendarSubscriptionInfoPageRest","org.projectforge.web.teamcal.admin.TeamCalEditForm","org.projectforge.web.teamcal.admin.TeamCalListPage"],"usedInFiles":[]}, {"i18nKey":"plugins.teamcal.subscription.timesheets","bundleName":"I18nResources","translation":"You can subscribe your time sheets in your personal calendar (e. g. Apple iCal oder Microsoft Outlook).","translationDE":"Über diese URL können Sie Ihre Zeitberichte in Ihrem persönlichen Kalender abonnieren (z. B. Apple iCal oder Microsoft Outlook).","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"plugins.teamcal.subscription.tooltip","bundleName":"I18nResources","translation":"You can subscribe this calendar in your personal calendar (e. g. Apple iCal oder Microsoft Outlook).","translationDE":"Über diese URL können Sie den Kalender in Ihrem persönlichen Kalender abonnieren (z. B. Apple iCal oder Microsoft Outlook).","usedInClasses":["org.projectforge.web.teamcal.admin.TeamCalEditForm","org.projectforge.web.teamcal.admin.TeamCalListPage"],"usedInFiles":[]}, + {"i18nKey":"plugins.teamcal.subscription.tooltip","bundleName":"I18nResources","translation":"You can subscribe this calendar in your personal calendar (e. g. Apple iCal oder Microsoft Outlook).","translationDE":"Über diese URL können Sie den Kalender in Ihrem persönlichen Kalender abonnieren (z. B. Apple iCal oder Microsoft Outlook).","usedInClasses":["org.projectforge.web.teamcal.admin.TeamCalEditForm","org.projectforge.web.teamcal.admin.TeamCalListPage"],"usedInFiles":[]}, {"i18nKey":"plugins.teamcal.switchToTeamEventButton","bundleName":"I18nResources","translation":"Switch to events","translationDE":"In Termin umwandeln","usedInClasses":["org.projectforge.rest.TimesheetPagesRest","org.projectforge.web.teamcal.integration.TeamcalTimesheetPluginComponentHook"],"usedInFiles":[]}, {"i18nKey":"plugins.teamcal.switchToTimesheetButton","bundleName":"I18nResources","translation":"Switch to time sheet","translationDE":"In Zeitbuchung umwandeln","usedInClasses":["org.projectforge.rest.calendar.CalEventPagesRest","org.projectforge.rest.calendar.TeamEventPagesRest","org.projectforge.web.teamcal.event.TeamEventEditPage"],"usedInFiles":[]}, {"i18nKey":"plugins.teamcal.timeSheetCalendar","bundleName":"I18nResources","translation":"Time sheets","translationDE":"Zeitbuchungen","usedInClasses":["org.projectforge.web.teamcal.dialog.TeamCalFilterDialog"],"usedInFiles":[]}, {"i18nKey":"plugins.teamcal.title","bundleName":"I18nResources","translation":"Title","translationDE":"Titel","usedInClasses":["org.projectforge.business.teamcal.admin.model.TeamCalDO","org.projectforge.rest.TeamCalPagesRest","org.projectforge.web.teamcal.admin.TeamCalEditForm","org.projectforge.web.teamcal.admin.TeamCalListPage"],"usedInFiles":[]}, - {"i18nKey":"plugins.teamcal.title.add","bundleName":"I18nResources","translation":"Add calendar","translationDE":"Kalender hinzufügen","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"plugins.teamcal.title.add","bundleName":"I18nResources","translation":"Add calendar","translationDE":"Kalender hinzufügen","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"plugins.teamcal.title.edit","bundleName":"I18nResources","translation":"Edit team calendar","translationDE":"Team-Kalender bearbeiten","usedInClasses":["org.projectforge.web.teamcal.admin.TeamCalEditPage","org.projectforge.web.teamcal.admin.TeamCalListPage"],"usedInFiles":[]}, {"i18nKey":"plugins.teamcal.title.heading","bundleName":"I18nResources","translation":"Calendar","translationDE":"Kalender","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"plugins.teamcal.title.list","bundleName":"I18nResources","translation":"List of calendars","translationDE":"Kalenderliste","usedInClasses":["org.projectforge.web.teamcal.admin.TeamCalEditPage","org.projectforge.web.teamcal.admin.TeamCalListPage"],"usedInFiles":[]}, + {"i18nKey":"poll","bundleName":"I18nResources","translation":"Poll","translationDE":"Umfrage","usedInClasses":["org.projectforge.business.poll.PollDO","org.projectforge.menu.builder.MenuItemDefId"],"usedInFiles":[]}, + {"i18nKey":"poll.access","bundleName":"I18nResources","translation":"Access","translationDE":"Zugriff","usedInClasses":["org.projectforge.business.poll.filter.PollAssignment"],"usedInFiles":[]}, + {"i18nKey":"poll.answer","bundleName":"I18nResources","translation":"Answer","translationDE":"Antwort","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.assignment","bundleName":"I18nResources","translation":"Assignment","translationDE":"Zuordnung","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.attendee","bundleName":"I18nResources","translation":"Attendee","translationDE":"Teilnehmer:in","usedInClasses":["org.projectforge.business.poll.filter.PollAssignment"],"usedInFiles":[]}, + {"i18nKey":"poll.attendees","bundleName":"I18nResources","translation":"Attendees","translationDE":"Teilnehmer:innen","usedInClasses":["org.projectforge.business.poll.PollDO","org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.button.addQuestion","bundleName":"I18nResources","translation":"Add own question","translationDE":"Eigene Frage hinzufügen","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.button.finish","bundleName":"I18nResources","translation":"Finish Poll","translationDE":"Umfrage beenden","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.button.micromataTemplate","bundleName":"I18nResources","translation":"Use Micromata Template","translationDE":"Micromata Template verwenden","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.confirmation.creation","bundleName":"I18nResources","translation":"Do you really want to create the poll? You won't be able to edit the questions afterwards.Also make sure to add attendees for your poll.","translationDE":"Möchtest du die Umfrage wirklich erstellen? Du kannst die Fragen danach nicht mehr bearbeiten.Stelle ebenfalls sicher, dass du Teilnehmer für deine Umfrage hinzugefügt hast.","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.confirmation.deleteAnswer","bundleName":"I18nResources","translation":"Do you really want to delete this answer?","translationDE":"Möchtest du diese Antwort wirklich löschen?","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.confirmation.deleteQuestion","bundleName":"I18nResources","translation":"Do you really want to delete this question?","translationDE":"Möchtest du diese Frage wirklich löschen?","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.confirmation.finish","bundleName":"I18nResources","translation":"Do you really want to finish this Poll?","translationDE":"Willst du die Umfrage wirklich beenden? ","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.date","bundleName":"I18nResources","translation":"Date","translationDE":"Datum","usedInClasses":["org.projectforge.business.poll.PollDO"],"usedInFiles":[]}, + {"i18nKey":"poll.deadline","bundleName":"I18nResources","translation":"Deadline","translationDE":"Antwortfrist","usedInClasses":["org.projectforge.business.poll.PollDO","org.projectforge.rest.poll.ResponsePageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.delegationAnswers","bundleName":"I18nResources","translation":"Answers of ","translationDE":"Antworten von ","usedInClasses":["org.projectforge.rest.poll.ResponsePageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.description","bundleName":"I18nResources","translation":"Description","translationDE":"Beschreibung","usedInClasses":["org.projectforge.business.poll.PollDO","org.projectforge.rest.poll.ResponsePageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.error.oneQuestionRequired","bundleName":"I18nResources","translation":"At least one question is required.","translationDE":"Mindestens eine Frage ist erforderlich.","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.exception.noAttendee","bundleName":"I18nResources","translation":"This user is not part of the poll.","translationDE":"Dieser Nutzer ist nicht Teil der Umfrage.","usedInClasses":["org.projectforge.rest.poll.ResponsePageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.export.response.poll","bundleName":"I18nResources","translation":"Export results","translationDE":"Ergebnisse exportieren","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.finished","bundleName":"I18nResources","translation":"Finished","translationDE":"Beendet","usedInClasses":["org.projectforge.business.poll.filter.PollState"],"usedInFiles":[]}, + {"i18nKey":"poll.fullAccessGroups","bundleName":"I18nResources","translation":"Full Access Groups","translationDE":"Gruppen mit Vollzugriff","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.fullAccessUsers","bundleName":"I18nResources","translation":"Full Access User","translationDE":"Benutzer:innen mit Vollzugriff","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.groupAttendees","bundleName":"I18nResources","translation":"Attendee Groups","translationDE":"Teilnehmergruppen","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.guide","bundleName":"I18nResources","translation":"Poll Guide","translationDE":"Anleitung","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.infopage","bundleName":"I18nResources","translation":"Info Page","translationDE":"Infoseite","usedInClasses":["org.projectforge.rest.poll.PollInfoPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.location","bundleName":"I18nResources","translation":"Location","translationDE":"Ort","usedInClasses":["org.projectforge.business.poll.PollDO","org.projectforge.rest.poll.ResponsePageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.mail.ended.content","bundleName":"I18nResources","translation":"

Dear Attendees,

We wanted to let you know that the poll \"{0}\" created by {1} has now ended. Thank you to everyone who participated and provided valuable input.

If you missed the deadline to submit your responses, we encourage you to still share your thoughts with us. While we may not be able to include your responses in the official results, your feedback is still valuable for future polls.

Thank you again for your participation.

Best regards,

{1}

","translationDE":"

Liebe Teilnehmerinnen und Teilnehmer,

wir möchten Sie darüber informieren, dass die Umfrage \"{0}\", erstellt von {1}, nun abgeschlossen ist. Vielen Dank an alle, die teilgenommen und wertvolles Feedback gegeben haben.

Falls Sie den Einsendeschluss verpasst haben, möchten wir Sie dennoch ermutigen, uns Ihre Gedanken mitzuteilen. Auch wenn wir Ihre Antworten möglicherweise nicht in den offiziellen Ergebnissen berücksichtigen können, ist Ihr Feedback dennoch wertvoll für zukünftige Umfragen und Initiativen.

Nochmals vielen Dank für Ihre Teilnahme.


Freundliche Grüße,

{1}

","usedInClasses":["org.projectforge.rest.poll.PollCronJobs"],"usedInFiles":[]}, + {"i18nKey":"poll.mail.ended.subject","bundleName":"I18nResources","translation":"Poll ended","translationDE":"Umfrage beendet","usedInClasses":["org.projectforge.rest.poll.PollCronJobs"],"usedInFiles":[]}, + {"i18nKey":"poll.mail.endingSoon.content","bundleName":"I18nResources","translation":"

Dear Attendees,

This is a friendly reminder that the poll \"{0}\" created by {1} is ending soon, on {2}. Please make sure to submit your responses before the deadline.

If you have not yet had a chance to participate, please take a few moments to do so before the poll closes. Your input is important and valued.

{3}

Thank you for your attention, and have a great day!

Best regards,

{1}

","translationDE":"

Liebe Teilnehmerinnen und Teilnehmer,

wir möchten Sie daran erinnern, dass die Umfrage \"{0}\", erstellt von {1}, bald endet, nämlich am {2}. Bitte achten Sie darauf, Ihre Antworten vor dem Ablaufdatum einzureichen.

Falls Sie noch nicht die Gelegenheit hatten, an der Umfrage teilzunehmen, nehmen Sie sich bitte einen Moment Zeit, um dies zu tun, bevor die Umfrage geschlossen wird. Ihre Meinung ist wichtig und wertvoll.

{3}

Vielen Dank für Ihre Aufmerksamkeit und einen schönen Tag!


Freundliche Grüße,

{1}

","usedInClasses":["org.projectforge.rest.poll.PollCronJobs"],"usedInFiles":[]}, + {"i18nKey":"poll.mail.endingSoon.subject","bundleName":"I18nResources","translation":"Poll ending in {0} days","translationDE":"Umfrage endet in {0} Tagen","usedInClasses":["org.projectforge.rest.poll.PollCronJobs"],"usedInFiles":[]}, + {"i18nKey":"poll.mail.update.content","bundleName":"I18nResources","translation":"

Dear Attendees,

We wanted to let you know that the poll \"{0}\" was edited recently.

If you already submitted your answers, you should check, if there were any major changes made.

Thank you again for your participation.

Best regards,

{1}

","translationDE":"

Liebe Teilnehmerinnen und Teilnehmer,

Wir möchten Ihnen mitteilen, dass die Umfrage \"{0}\" kürzlich bearbeitet wurde.

Falls Sie bereits Ihre Antworten eingereicht haben, sollten Sie überprüfen, ob wesentliche Änderungen vorgenommen wurden.

Nochmals vielen Dank für Ihre Teilnahme.

Freundliche Grüße,

{1}

","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.mail.update.subject","bundleName":"I18nResources","translation":"Poll was edited","translationDE":"Umfrage wurde bearbeitet","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.other","bundleName":"I18nResources","translation":"Other","translationDE":"Andere","usedInClasses":["org.projectforge.business.poll.filter.PollAssignment"],"usedInFiles":[]}, + {"i18nKey":"poll.owner","bundleName":"I18nResources","translation":"Owner","translationDE":"Eigentümer:in","usedInClasses":["org.projectforge.business.poll.PollDO","org.projectforge.business.poll.filter.PollAssignment","org.projectforge.rest.poll.PollPageRest","org.projectforge.rest.poll.ResponsePageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.popup.closed","bundleName":"I18nResources","translation":"The poll was already closed.","translationDE":"Umfrage wurde bereits beendet","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"poll.question","bundleName":"I18nResources","translation":"Question","translationDE":"Frage","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.question.textQuestion","bundleName":"I18nResources","translation":"Text Question","translationDE":"Textfrage","usedInClasses":["org.projectforge.rest.poll.ResponsePageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.questionType","bundleName":"I18nResources","translation":"Question Type","translationDE":"Fragen Typ","usedInClasses":["org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.respond","bundleName":"I18nResources","translation":"Send responses","translationDE":"Antworten abschicken","usedInClasses":["org.projectforge.rest.poll.ResponsePageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.response.mail.update.content","bundleName":"I18nResources","translation":"

Dear {0},

I wanted to inform you that Person {2} has updated their answer to Poll {1}.

If you were not notified about this,

I recommend reaching out to the person directly or adjusting your results accordingly.

You can modify your response until \"{3}\".

Best regards,

{2}

","translationDE":"

Dear {0},

I wanted to inform you that Person {2} has updated their answer to Poll {1}.

If you were not notified about this,

I recommend reaching out to the person directly or adjusting your results accordingly.

You can modify your response until \"{3}\".

Best regards,

{2}

","usedInClasses":["org.projectforge.rest.poll.ResponsePageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.response.mail.update.subject","bundleName":"I18nResources","translation":"Response was edited by {0}","translationDE":"Response was edited by {0}","usedInClasses":["org.projectforge.rest.poll.ResponsePageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.response.page","bundleName":"I18nResources","translation":"Poll Response Page","translationDE":"Seite zur Umfrageantwort","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"poll.response.title","bundleName":"I18nResources","translation":"Poll Response Page","translationDE":"Seite zur Umfrageantwort","usedInClasses":["org.projectforge.rest.poll.ResponsePageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.running","bundleName":"I18nResources","translation":"Running","translationDE":"Aktiv","usedInClasses":["org.projectforge.business.poll.filter.PollState"],"usedInFiles":[]}, + {"i18nKey":"poll.selectUser","bundleName":"I18nResources","translation":"Select user","translationDE":"Nutzer auswählen","usedInClasses":["org.projectforge.rest.poll.ResponsePageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.state","bundleName":"I18nResources","translation":"State","translationDE":"Status","usedInClasses":["org.projectforge.business.poll.PollDO","org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.title","bundleName":"I18nResources","translation":"Title","translationDE":"Titel","usedInClasses":["org.projectforge.business.poll.PollDO","org.projectforge.rest.poll.PollPageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.title.add","bundleName":"I18nResources","translation":"Create new Poll","translationDE":"Neue Umfrage erstellen","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"poll.title.edit","bundleName":"I18nResources","translation":"Edit Poll","translationDE":"Umfrage bearbeiten","usedInClasses":["org.projectforge.rest.poll.ResponsePageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.title.list","bundleName":"I18nResources","translation":"Polls","translationDE":"Umfragen","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"poll.userDelegation","bundleName":"I18nResources","translation":"User delegation","translationDE":"Für andere Nutzer abstimmen","usedInClasses":["org.projectforge.rest.poll.ResponsePageRest"],"usedInFiles":[]}, + {"i18nKey":"poll.yourAnswers","bundleName":"I18nResources","translation":"Your answers","translationDE":"Deine Antworten","usedInClasses":["org.projectforge.rest.poll.ResponsePageRest"],"usedInFiles":[]}, {"i18nKey":"preview","bundleName":"I18nResources","translation":"Preview","translationDE":"Vorschau","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"printView","bundleName":"I18nResources","translation":"print view","translationDE":"Druckansicht","usedInClasses":["org.projectforge.rest.AddressPagesRest","org.projectforge.web.address.AddressListPage"],"usedInFiles":[]}, {"i18nKey":"priority","bundleName":"I18nResources","translation":"Priority","translationDE":"Priorität","usedInClasses":["org.projectforge.business.task.TaskDO","org.projectforge.plugins.todo.ToDoDO","org.projectforge.plugins.todo.ToDoEditForm","org.projectforge.plugins.todo.ToDoListPage","org.projectforge.plugins.todo.rest.ToDoPagesRest","org.projectforge.rest.hr.HRPlanningListPagesRest","org.projectforge.rest.task.TaskPagesRest","org.projectforge.web.humanresources.HRPlanningEditForm","org.projectforge.web.humanresources.HRPlanningListPage","org.projectforge.web.task.TaskEditForm","org.projectforge.web.task.TaskListPage","org.projectforge.web.task.TaskTreeBuilder"],"usedInFiles":["./projectforge-business/src/main/resources/mail/todoChangeNotification.html"]}, @@ -2021,10 +2075,10 @@ {"i18nKey":"priority.without","bundleName":"I18nResources","translation":"without","translationDE":"ohne","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"projectmanagement.personDays","bundleName":"I18nResources","translation":"Person days","translationDE":"Personentage","usedInClasses":["org.projectforge.business.fibu.AuftragsPositionDO","org.projectforge.rest.fibu.AuftragPagesRest","org.projectforge.web.fibu.AuftragEditForm"],"usedInFiles":[]}, {"i18nKey":"projectmanagement.personDays.short","bundleName":"I18nResources","translation":"pd","translationDE":"PT","usedInClasses":["org.projectforge.business.fibu.OrderExport","org.projectforge.rest.task.Consumption","org.projectforge.web.fibu.AuftragListPage","org.projectforge.web.fibu.OrderPositionsPanel","org.projectforge.web.task.TaskListPage"],"usedInFiles":[]}, - {"i18nKey":"question.deleteQuestion","bundleName":"I18nResources","translation":"Do your really want to delete this object finally?","translationDE":"Soll das Objekt wirklich unwiderruflich gelöscht werden?","usedInClasses":["org.projectforge.ui.UIButton","org.projectforge.web.wicket.AbstractEditPage","org.projectforge.web.wicket.WicketUtils"],"usedInFiles":[]}, - {"i18nKey":"question.forceDeleteQuestion","bundleName":"I18nResources","translation":"ATTENTION!!! Do you really want to destroy this object including all history entries? This option is irrevocable!!!!!","translationDE":"ACHTUNG!!! Soll das Object tatsächlich uwiderruflich gelöscht werden? Es werden auch alle Einträge aus der Änderungshistorie zu diesem Object unwiderruflich gelöscht!!!!!!","usedInClasses":["org.projectforge.ui.UIButton"],"usedInFiles":[]}, - {"i18nKey":"question.markAsDeletedQuestion","bundleName":"I18nResources","translation":"Do you want to mark this object as deleted?","translationDE":"Soll das Objekt als gelöscht markiert werden?","usedInClasses":["org.projectforge.ui.UIButton","org.projectforge.web.wicket.AbstractEditPage","org.projectforge.web.wicket.WicketUtils"],"usedInFiles":[]}, - {"i18nKey":"question.massUpdateQuestion","bundleName":"I18nResources","translation":"Do you really want to update all objects now?","translationDE":"Sollen nun alle Objekte geändert werden?","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"question.deleteQuestion","bundleName":"I18nResources","translation":"Do your really want to delete this object finally?","translationDE":"Soll das Objekt wirklich unwiderruflich gelöscht werden?","usedInClasses":["org.projectforge.ui.UIButton","org.projectforge.web.wicket.AbstractEditPage","org.projectforge.web.wicket.WicketUtils"],"usedInFiles":[]}, + {"i18nKey":"question.forceDeleteQuestion","bundleName":"I18nResources","translation":"ATTENTION!!! Do you really want to destroy this object including all history entries? This option is irrevocable!!!!!","translationDE":"ACHTUNG!!! Soll das Object tatsächlich uwiderruflich gelöscht werden? Es werden auch alle Einträge aus der Änderungshistorie zu diesem Object unwiderruflich gelöscht!!!!!!","usedInClasses":["org.projectforge.ui.UIButton"],"usedInFiles":[]}, + {"i18nKey":"question.markAsDeletedQuestion","bundleName":"I18nResources","translation":"Do you want to mark this object as deleted?","translationDE":"Soll das Objekt als gelöscht markiert werden?","usedInClasses":["org.projectforge.ui.UIButton","org.projectforge.web.wicket.AbstractEditPage","org.projectforge.web.wicket.WicketUtils"],"usedInFiles":[]}, + {"i18nKey":"question.massUpdateQuestion","bundleName":"I18nResources","translation":"Do you really want to update all objects now?","translationDE":"Sollen nun alle Objekte geändert werden?","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"quickselect","bundleName":"I18nResources","translation":"Quick-select","translationDE":"Schnellauswahl","usedInClasses":["org.projectforge.web.task.TaskSelectPanel"],"usedInFiles":[]}, {"i18nKey":"recalculate","bundleName":"I18nResources","translation":"Recalculate","translationDE":"Neu berechnen","usedInClasses":["org.projectforge.rest.VacationAccountPageRest","org.projectforge.web.fibu.RechnungCostEditTablePanel","org.projectforge.web.humanresources.HRPlanningEditForm"],"usedInFiles":[]}, {"i18nKey":"recursive","bundleName":"I18nResources","translation":"recursive","translationDE":"rekursiv","usedInClasses":["org.projectforge.business.timesheet.TimesheetFilter","org.projectforge.framework.access.GroupTaskAccessDO","org.projectforge.web.access.AccessEditForm","org.projectforge.web.access.AccessListPage","org.projectforge.web.timesheet.TimesheetListForm"],"usedInFiles":[]}, @@ -2038,25 +2092,25 @@ {"i18nKey":"resumption","bundleName":"I18nResources","translation":"Resumption","translationDE":"Wiedervorlage","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"save","bundleName":"I18nResources","translation":"Save","translationDE":"Speichern","usedInClasses":["org.projectforge.model.rest.RestPaths","org.projectforge.plugins.eed.wicket.EmployeeListEditForm","org.projectforge.rest.calendar.CalendarSettingsPageRest","org.projectforge.ui.LayoutUtils","org.projectforge.web.gantt.GanttChartEditTreeTablePanel","org.projectforge.web.wicket.flowlayout.IconType"],"usedInFiles":["./projectforge-wicket/src/main/java/org/projectforge/web/gantt/GanttChartEditTreeTablePanel.html"]}, {"i18nKey":"scripting.download.filename","bundleName":"I18nResources","translation":"Result file","translationDE":"Ergbnisdatei","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"scripting.download.filename.additional","bundleName":"I18nResources","translation":"Download only available for a few minutes: until {0}.","translationDE":"Download nur wenige Minuten verfügbar: bis {0}.","usedInClasses":["org.projectforge.rest.core.DownloadFileSupport"],"usedInFiles":[]}, - {"i18nKey":"scripting.download.filename.info","bundleName":"I18nResources","translation":"File generated by last script run.","translationDE":"Downloaddatei der letzten Scriptausführung.","usedInClasses":["org.projectforge.rest.scripting.AbstractScriptExecutePageRest"],"usedInFiles":[]}, + {"i18nKey":"scripting.download.filename.additional","bundleName":"I18nResources","translation":"Download only available for a few minutes: until {0}.","translationDE":"Download nur wenige Minuten verfügbar: bis {0}.","usedInClasses":["org.projectforge.rest.core.DownloadFileSupport"],"usedInFiles":[]}, + {"i18nKey":"scripting.download.filename.info","bundleName":"I18nResources","translation":"File generated by last script run.","translationDE":"Downloaddatei der letzten Scriptausführung.","usedInClasses":["org.projectforge.rest.scripting.AbstractScriptExecutePageRest"],"usedInFiles":[]}, {"i18nKey":"scripting.myScript.list","bundleName":"I18nResources","translation":"My scripts","translationDE":"Meine Scripte","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"scripting.script","bundleName":"I18nResources","translation":"Script","translationDE":"Script","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"scripting.script.availableVariables","bundleName":"I18nResources","translation":"Availabe variables","translationDE":"Verfügbare Variablen","usedInClasses":["org.projectforge.rest.scripting.AbstractScriptExecutePageRest","org.projectforge.rest.scripting.ScriptPagesRest"],"usedInFiles":[]}, - {"i18nKey":"scripting.script.description.tooltip","bundleName":"I18nResources","translation":"Markdown format is supported.","translationDE":"Markdown-Format wird unterstützt.","usedInClasses":["org.projectforge.business.scripting.ScriptDO"],"usedInFiles":[]}, + {"i18nKey":"scripting.script.availableVariables","bundleName":"I18nResources","translation":"Available variables","translationDE":"Verfügbare Variablen","usedInClasses":["org.projectforge.rest.scripting.AbstractScriptExecutePageRest","org.projectforge.rest.scripting.ScriptPagesRest"],"usedInFiles":[]}, + {"i18nKey":"scripting.script.description.tooltip","bundleName":"I18nResources","translation":"Markdown format is supported.","translationDE":"Markdown-Format wird unterstützt.","usedInClasses":["org.projectforge.business.scripting.ScriptDO"],"usedInFiles":[]}, {"i18nKey":"scripting.script.downloadBackups","bundleName":"I18nResources","translation":"Download backups","translationDE":"Download Backups","usedInClasses":["org.projectforge.rest.scripting.ScriptPagesRest"],"usedInFiles":[]}, {"i18nKey":"scripting.script.downloadEffectiveScript","bundleName":"I18nResources","translation":"Download effective script","translationDE":"Download Effektivscript","usedInClasses":["org.projectforge.rest.scripting.ScriptPagesRest"],"usedInFiles":[]}, - {"i18nKey":"scripting.script.downloadEffectiveScript.info","bundleName":"I18nResources","translation":"The effective script is the script with resolved includes etc. executed by the scripting engine.","translationDE":"Das Effektivscript ist das Script mit aufgelösten Include-Dateien etc., welches so von der Scripting-Engine so ausgeführt wird.","usedInClasses":["org.projectforge.rest.scripting.ScriptPagesRest"],"usedInFiles":[]}, - {"i18nKey":"scripting.script.editForm.file.tooltip","bundleName":"I18nResources","translation":"Within the script the file is available through 'script.file' and the name of the file through 'script.filename'. This is only for backward compability for older scripts, generated by classical version.","translationDE":"Die Datei ist im Script über 'script.file' und der Dateiname über 'script.filename' erreichbar. Diese Datei ist aus Abwährtskompatibiltätsgründen noch nutzbar für ältere Scripte.","usedInClasses":["org.projectforge.business.scripting.ScriptDO"],"usedInFiles":[]}, + {"i18nKey":"scripting.script.downloadEffectiveScript.info","bundleName":"I18nResources","translation":"The effective script is the script with resolved includes etc. executed by the scripting engine.","translationDE":"Das Effektivscript ist das Script mit aufgelösten Include-Dateien etc., welches so von der Scripting-Engine so ausgeführt wird.","usedInClasses":["org.projectforge.rest.scripting.ScriptPagesRest"],"usedInFiles":[]}, + {"i18nKey":"scripting.script.editForm.file.tooltip","bundleName":"I18nResources","translation":"Within the script the file is available through 'script.file' and the name of the file through 'script.filename'. This is only for backward compability for older scripts, generated by classical version.","translationDE":"Die Datei ist im Script über 'script.file' und der Dateiname über 'script.filename' erreichbar. Diese Datei ist aus Abwährtskompatibiltätsgründen noch nutzbar für ältere Scripte.","usedInClasses":["org.projectforge.business.scripting.ScriptDO"],"usedInFiles":[]}, {"i18nKey":"scripting.script.error.notFound","bundleName":"I18nResources","translation":"Script not found.","translationDE":"Script not found.","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"scripting.script.examples","bundleName":"I18nResources","translation":"Examples","translationDE":"Beispiele","usedInClasses":["org.projectforge.rest.scripting.ScriptExecutePageRest"],"usedInFiles":[]}, {"i18nKey":"scripting.script.executableByGroups","bundleName":"I18nResources","translation":"Allowed groups","translationDE":"Berechtigte Gruppen","usedInClasses":["org.projectforge.business.scripting.ScriptDO","org.projectforge.rest.scripting.ScriptPagesRest"],"usedInFiles":[]}, - {"i18nKey":"scripting.script.executableByGroups.info","bundleName":"I18nResources","translation":"Users of these groups are allowed to execute this script (but they can't see or modify the sources of this script).","translationDE":"Angehörige dieser Gruppen dürfen dieses Script ausführen (nicht einsehen oder ändern).","usedInClasses":["org.projectforge.business.scripting.ScriptDO","org.projectforge.rest.scripting.ScriptPagesRest"],"usedInFiles":[]}, + {"i18nKey":"scripting.script.executableByGroups.info","bundleName":"I18nResources","translation":"Users of these groups are allowed to execute this script (but they can't see or modify the sources of this script).","translationDE":"Angehörige dieser Gruppen dürfen dieses Script ausführen (nicht einsehen oder ändern).","usedInClasses":["org.projectforge.business.scripting.ScriptDO","org.projectforge.rest.scripting.ScriptPagesRest"],"usedInFiles":[]}, {"i18nKey":"scripting.script.executableByUsers","bundleName":"I18nResources","translation":"Allowed users","translationDE":"Berechtigte Benutzer:innen","usedInClasses":["org.projectforge.business.scripting.ScriptDO","org.projectforge.rest.scripting.ScriptExecutePageRest","org.projectforge.rest.scripting.ScriptPagesRest"],"usedInFiles":[]}, - {"i18nKey":"scripting.script.executableByUsers.info","bundleName":"I18nResources","translation":"These users are allowed to execute this script (but they can't see or modify the sources of this script).","translationDE":"Diese dürfen dieses Script ausführen (nicht einsehen oder ändern).","usedInClasses":["org.projectforge.business.scripting.ScriptDO","org.projectforge.rest.scripting.ScriptPagesRest"],"usedInFiles":[]}, - {"i18nKey":"scripting.script.execute","bundleName":"I18nResources","translation":"Execute script","translationDE":"Script ausführen","usedInClasses":["org.projectforge.rest.scripting.AbstractScriptExecutePageRest","org.projectforge.rest.scripting.ScriptPagesRest"],"usedInFiles":[]}, - {"i18nKey":"scripting.script.executeAsUser","bundleName":"I18nResources","translation":"Execute as user","translationDE":"Als andere:r User:in ausführen","usedInClasses":["org.projectforge.business.scripting.ScriptDO","org.projectforge.rest.scripting.ScriptPagesRest"],"usedInFiles":[]}, - {"i18nKey":"scripting.script.executeAsUser.info","bundleName":"I18nResources","translation":"The script is executed with all rights of the given user. Be careful! The execute-as functionality must be enabled inside the script as well to avoid mis-configurations.","translationDE":"Das Script wird mit ALLEN Rechten dieser oder der Benutzer:in ausgeführt. Bitte vorsichtig verwenden! Im Script selber muss diese Funktion auch aktiviert werden, um Fehlkonfigurationen zu vermeiden.","usedInClasses":["org.projectforge.business.scripting.ScriptDO"],"usedInFiles":[]}, + {"i18nKey":"scripting.script.executableByUsers.info","bundleName":"I18nResources","translation":"These users are allowed to execute this script (but they can't see or modify the sources of this script).","translationDE":"Diese dürfen dieses Script ausführen (nicht einsehen oder ändern).","usedInClasses":["org.projectforge.business.scripting.ScriptDO","org.projectforge.rest.scripting.ScriptPagesRest"],"usedInFiles":[]}, + {"i18nKey":"scripting.script.execute","bundleName":"I18nResources","translation":"Execute script","translationDE":"Script ausführen","usedInClasses":["org.projectforge.rest.scripting.AbstractScriptExecutePageRest","org.projectforge.rest.scripting.ScriptPagesRest"],"usedInFiles":[]}, + {"i18nKey":"scripting.script.executeAsUser","bundleName":"I18nResources","translation":"Execute as user","translationDE":"Als andere:r User:in ausführen","usedInClasses":["org.projectforge.business.scripting.ScriptDO","org.projectforge.rest.scripting.ScriptPagesRest"],"usedInFiles":[]}, + {"i18nKey":"scripting.script.executeAsUser.info","bundleName":"I18nResources","translation":"The script is executed with all rights of the given user. Be careful! The execute-as functionality must be enabled inside the script as well to avoid mis-configurations.","translationDE":"Das Script wird mit ALLEN Rechten dieser oder der Benutzer:in ausgeführt. Bitte vorsichtig verwenden! Im Script selber muss diese Funktion auch aktiviert werden, um Fehlkonfigurationen zu vermeiden.","usedInClasses":["org.projectforge.business.scripting.ScriptDO"],"usedInFiles":[]}, {"i18nKey":"scripting.script.includes","bundleName":"I18nResources","translation":"Embedded scripts","translationDE":"Eingebundene Scripts","usedInClasses":["org.projectforge.business.scripting.ScriptDO"],"usedInFiles":[]}, {"i18nKey":"scripting.script.name","bundleName":"I18nResources","translation":"Name","translationDE":"Name","usedInClasses":["org.projectforge.business.scripting.ScriptDO","org.projectforge.rest.scripting.AbstractScriptExecutePageRest"],"usedInFiles":[]}, {"i18nKey":"scripting.script.parameter","bundleName":"I18nResources","translation":"Parameter","translationDE":"Parameter","usedInClasses":["org.projectforge.rest.scripting.AbstractScriptExecutePageRest","org.projectforge.rest.scripting.MyScriptPagesRest","org.projectforge.rest.scripting.ScriptPagesRest"],"usedInFiles":[]}, @@ -2087,16 +2141,16 @@ {"i18nKey":"search.lastHours","bundleName":"I18nResources","translation":"Last {0} hours","translationDE":"Letzte {0} Stunden","usedInClasses":["org.projectforge.rest.core.AbstractPagesRest","org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]}, {"i18nKey":"search.lastMinute","bundleName":"I18nResources","translation":"Last minute","translationDE":"Letzte Minute","usedInClasses":["org.projectforge.rest.core.AbstractPagesRest","org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]}, {"i18nKey":"search.lastMinutes","bundleName":"I18nResources","translation":"Last {0} minutes","translationDE":"Letzte {0} Minuten","usedInClasses":["org.projectforge.rest.core.AbstractPagesRest","org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]}, - {"i18nKey":"search.lucene.expression","bundleName":"I18nResources","translation":"Modified expression for search machine:","translationDE":"Modifizierter Ausdruck für Suchmaschine:","usedInClasses":["org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]}, - {"i18nKey":"search.maxRowsExceeded","bundleName":"I18nResources","translation":"Maximum number {0} of result size exceeded. Result list is truncated.","translationDE":"Die Maximalgröße {0} der Ergebnisliste ist überschritten und das Ergebnis enthält nicht alle gefundenen Elemente.","usedInClasses":["org.projectforge.rest.core.ResultSet","org.projectforge.web.wicket.AbstractListPage"],"usedInFiles":[]}, - {"i18nKey":"search.nextDays","bundleName":"I18nResources","translation":"Next {0} days","translationDE":"Nächste {0} Tage","usedInClasses":["org.projectforge.plugins.liquidityplanning.LiquidityEntryListForm"],"usedInFiles":[]}, + {"i18nKey":"search.lucene.expression","bundleName":"I18nResources","translation":"Modified expression for search machine:","translationDE":"Modifizierter Ausdruck für Suchmaschine:","usedInClasses":["org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]}, + {"i18nKey":"search.maxRowsExceeded","bundleName":"I18nResources","translation":"Maximum number {0} of result size exceeded. Result list is truncated.","translationDE":"Die Maximalgröße {0} der Ergebnisliste ist überschritten und das Ergebnis enthält nicht alle gefundenen Elemente.","usedInClasses":["org.projectforge.rest.core.ResultSet","org.projectforge.web.wicket.AbstractListPage"],"usedInFiles":[]}, + {"i18nKey":"search.nextDays","bundleName":"I18nResources","translation":"Next {0} days","translationDE":"Nächste {0} Tage","usedInClasses":["org.projectforge.plugins.liquidityplanning.LiquidityEntryListForm"],"usedInFiles":[]}, {"i18nKey":"search.parseError","bundleName":"I18nResources","translation":"Error while parsing search string: ''{0}''","translationDE":"Fehler beim Vearbeiten des Suchtexts: ''{0}''","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"search.periodOfModification","bundleName":"I18nResources","translation":"Period of modification","translationDE":"Änderungszeitraum","usedInClasses":["org.projectforge.web.core.SearchForm","org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]}, + {"i18nKey":"search.periodOfModification","bundleName":"I18nResources","translation":"Period of modification","translationDE":"Änderungszeitraum","usedInClasses":["org.projectforge.web.core.SearchForm","org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]}, {"i18nKey":"search.search","bundleName":"I18nResources","translation":"Search","translationDE":"Suche","usedInClasses":["org.projectforge.rest.TimesheetPagesRest"],"usedInFiles":[]}, {"i18nKey":"search.searchHistory","bundleName":"I18nResources","translation":"History","translationDE":"Historie","usedInClasses":["org.projectforge.web.core.SearchForm","org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]}, - {"i18nKey":"search.searchHistory.additional.tooltip","bundleName":"I18nResources","translation":"If enabled all history entries will be searched for the given search string too.","translationDE":"Wenn gewählt, werden auch alle Änderungshistorieneinträge mit dem Suchausdruck durchsucht.","usedInClasses":["org.projectforge.web.core.SearchForm","org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]}, + {"i18nKey":"search.searchHistory.additional.tooltip","bundleName":"I18nResources","translation":"If enabled all history entries will be searched for the given search string too.","translationDE":"Wenn gewählt, werden auch alle Änderungshistorieneinträge mit dem Suchausdruck durchsucht.","usedInClasses":["org.projectforge.web.core.SearchForm","org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]}, {"i18nKey":"search.sinceYesterday","bundleName":"I18nResources","translation":"Since yesterday","translationDE":"Seit gestern","usedInClasses":["org.projectforge.rest.core.AbstractPagesRest","org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]}, - {"i18nKey":"search.string.info","bundleName":"I18nResources","translation":"Use ''*'' for wild cards (automatically appended if token contraints only alpha numerical characters, white spaces or \"@._-\"), ''-string'' for negation, ''fieldname:string'' for searching in fields, for further information see help pages (lucene is used as search engine).\nSearch in fields: {0}.","translationDE":"''*'' für Teilausdrücke (automatisch angehängt, wenn nur alphanumerische Ausdrücke angegeben inkl. Leerzeichen und \"@._-\"), ''-ausdruck'' für Ausschlüsse, ''Feldname:Ausdruck'' für Teilwortsuche, s. auch Dokumentation (es wird Lucene als Suchmaschine eingesetzt).\nSuche in den Feldern: {0}.","usedInClasses":["org.projectforge.web.wicket.AbstractListPage"],"usedInFiles":[]}, + {"i18nKey":"search.string.info","bundleName":"I18nResources","translation":"Use ''*'' for wild cards (automatically appended if token contraints only alpha numerical characters, white spaces or \"@._-\"), ''-string'' for negation, ''fieldname:string'' for searching in fields, for further information see help pages (lucene is used as search engine).\nSearch in fields: {0}.","translationDE":"''*'' für Teilausdrücke (automatisch angehängt, wenn nur alphanumerische Ausdrücke angegeben inkl. Leerzeichen und \"@._-\"), ''-ausdruck'' für Ausschlüsse, ''Feldname:Ausdruck'' für Teilwortsuche, s. auch Dokumentation (es wird Lucene als Suchmaschine eingesetzt).\nSuche in den Feldern: {0}.","usedInClasses":["org.projectforge.web.wicket.AbstractListPage"],"usedInFiles":[]}, {"i18nKey":"search.string.info.title","bundleName":"I18nResources","translation":"Full text search","translationDE":"Volltextsuche","usedInClasses":["org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]}, {"i18nKey":"search.title","bundleName":"I18nResources","translation":"Search","translationDE":"Suche","usedInClasses":["org.projectforge.web.core.SearchPage"],"usedInFiles":[]}, {"i18nKey":"search.toDetailedSearch","bundleName":"I18nResources","translation":"To detailed search","translationDE":"Zur Detailsuche","usedInClasses":[],"usedInFiles":["./projectforge-wicket/src/main/java/org/projectforge/web/core/SearchAreaPanel.html"]}, @@ -2138,11 +2192,11 @@ {"i18nKey":"space.title.list","bundleName":"I18nResources","translation":"Spaces","translationDE":"Spaces","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"sql","bundleName":"I18nResources","translation":"SQL","translationDE":"SQL","usedInClasses":["org.projectforge.common.MimeType","org.projectforge.web.admin.GroovyConsoleForm","org.projectforge.web.admin.LuceneConsoleForm","org.projectforge.web.admin.SqlConsoleForm"],"usedInFiles":[]}, {"i18nKey":"statistics","bundleName":"I18nResources","translation":"Statistics","translationDE":"Statistik","usedInClasses":["org.projectforge.plugins.liquidityplanning.LiquidityEntryListForm","org.projectforge.web.core.importstorage.AbstractImportStoragePanel","org.projectforge.web.fibu.AbstractRechnungListForm","org.projectforge.web.fibu.AuftragListForm","org.projectforge.web.fibu.MonthlyEmployeeReportPage"],"usedInFiles":["./projectforge-wicket/src/main/java/org/projectforge/web/core/importstorage/AbstractImportStoragePanel.html"]}, - {"i18nKey":"status","bundleName":"I18nResources","translation":"Status","translationDE":"Status","usedInClasses":["org.projectforge.business.book.BookDO","org.projectforge.business.fibu.AuftragDO","org.projectforge.business.fibu.AuftragsPositionDO","org.projectforge.business.fibu.EmployeeDO","org.projectforge.business.fibu.EmployeeDao","org.projectforge.business.fibu.KontoDO","org.projectforge.business.fibu.KundeDO","org.projectforge.business.fibu.OrderExport","org.projectforge.business.fibu.ProjektDO","org.projectforge.business.fibu.ProjektDao","org.projectforge.business.fibu.kost.Kost1DO","org.projectforge.business.fibu.kost.Kost2DO","org.projectforge.business.fibu.kost.KostZuweisungExport","org.projectforge.business.humanresources.HRPlanningEntryDO","org.projectforge.business.orga.ContractDO","org.projectforge.business.orga.ContractDao","org.projectforge.business.task.TaskDO","org.projectforge.business.task.TaskDao","org.projectforge.business.teamcal.servlet.TeamCalResponseServlet","org.projectforge.business.vacation.repository.VacationDao","org.projectforge.framework.jobs.AbstractJob","org.projectforge.framework.persistence.attr.impl.InternalAttrSchemaConstants","org.projectforge.plugins.todo.ToDoDao","org.projectforge.plugins.todo.ToDoEditForm","org.projectforge.plugins.todo.ToDoListPage","org.projectforge.plugins.todo.rest.ToDoPagesRest","org.projectforge.rest.BookPagesRest","org.projectforge.rest.GroupPagesRest","org.projectforge.rest.UserPagesRest","org.projectforge.rest.VacationPagesRest","org.projectforge.rest.fibu.CustomerPagesRest","org.projectforge.rest.fibu.EmployeePagesRest","org.projectforge.rest.fibu.KontoPagesRest","org.projectforge.rest.fibu.ProjectPagesRest","org.projectforge.rest.fibu.RechnungMultiSelectedPageRest","org.projectforge.rest.fibu.RechnungPagesRest","org.projectforge.rest.importer.AbstractImportPageRest","org.projectforge.rest.orga.ContractPagesRest","org.projectforge.rest.task.TaskPagesRest","org.projectforge.rest.task.TaskServicesRest","org.projectforge.web.fibu.AuftragEditForm","org.projectforge.web.fibu.AuftragListPage","org.projectforge.web.fibu.CustomerEditForm","org.projectforge.web.fibu.CustomerListPage","org.projectforge.web.fibu.KontoEditForm","org.projectforge.web.fibu.KontoListPage","org.projectforge.web.fibu.Kost1EditForm","org.projectforge.web.fibu.Kost1ListPage","org.projectforge.web.fibu.Kost2EditForm","org.projectforge.web.fibu.Kost2ListPage","org.projectforge.web.fibu.ProjektEditForm","org.projectforge.web.fibu.ProjektListPage","org.projectforge.web.fibu.RechnungEditForm","org.projectforge.web.fibu.RechnungListPage","org.projectforge.web.humanresources.HRPlanningEditForm","org.projectforge.web.task.TaskEditForm","org.projectforge.web.task.TaskListPage","org.projectforge.web.task.TaskTreeBuilder","org.projectforge.web.teamcal.event.TeamAttendeesPanel"],"usedInFiles":["./projectforge-business/src/main/resources/mail/orderChangeNotification.html","./projectforge-wicket/src/main/java/org/projectforge/web/teamcal/event/TeamAttendeesPanel.html"]}, + {"i18nKey":"status","bundleName":"I18nResources","translation":"Status","translationDE":"Status","usedInClasses":["org.projectforge.business.book.BookDO","org.projectforge.business.fibu.AuftragDO","org.projectforge.business.fibu.AuftragsPositionDO","org.projectforge.business.fibu.EmployeeDO","org.projectforge.business.fibu.EmployeeDao","org.projectforge.business.fibu.KontoDO","org.projectforge.business.fibu.KundeDO","org.projectforge.business.fibu.OrderExport","org.projectforge.business.fibu.ProjektDO","org.projectforge.business.fibu.ProjektDao","org.projectforge.business.fibu.kost.Kost1DO","org.projectforge.business.fibu.kost.Kost2DO","org.projectforge.business.fibu.kost.KostZuweisungExport","org.projectforge.business.humanresources.HRPlanningEntryDO","org.projectforge.business.orga.ContractDO","org.projectforge.business.orga.ContractDao","org.projectforge.business.task.TaskDO","org.projectforge.business.task.TaskDao","org.projectforge.business.teamcal.servlet.TeamCalResponseServlet","org.projectforge.business.vacation.repository.VacationDao","org.projectforge.framework.jobs.AbstractJob","org.projectforge.framework.persistence.attr.impl.InternalAttrSchemaConstants","org.projectforge.plugins.todo.ToDoDao","org.projectforge.plugins.todo.ToDoEditForm","org.projectforge.plugins.todo.ToDoListPage","org.projectforge.plugins.todo.rest.ToDoPagesRest","org.projectforge.rest.BookPagesRest","org.projectforge.rest.GroupPagesRest","org.projectforge.rest.UserPagesRest","org.projectforge.rest.VacationPagesRest","org.projectforge.rest.fibu.CustomerPagesRest","org.projectforge.rest.fibu.EmployeePagesRest","org.projectforge.rest.fibu.KontoPagesRest","org.projectforge.rest.fibu.ProjectPagesRest","org.projectforge.rest.fibu.RechnungMultiSelectedPageRest","org.projectforge.rest.fibu.RechnungPagesRest","org.projectforge.rest.importer.AbstractImportPageRest","org.projectforge.rest.orga.ContractPagesRest","org.projectforge.rest.poll.PollPageRest","org.projectforge.rest.task.TaskPagesRest","org.projectforge.rest.task.TaskServicesRest","org.projectforge.web.fibu.AuftragEditForm","org.projectforge.web.fibu.AuftragListPage","org.projectforge.web.fibu.CustomerEditForm","org.projectforge.web.fibu.CustomerListPage","org.projectforge.web.fibu.KontoEditForm","org.projectforge.web.fibu.KontoListPage","org.projectforge.web.fibu.Kost1EditForm","org.projectforge.web.fibu.Kost1ListPage","org.projectforge.web.fibu.Kost2EditForm","org.projectforge.web.fibu.Kost2ListPage","org.projectforge.web.fibu.ProjektEditForm","org.projectforge.web.fibu.ProjektListPage","org.projectforge.web.fibu.RechnungEditForm","org.projectforge.web.fibu.RechnungListPage","org.projectforge.web.humanresources.HRPlanningEditForm","org.projectforge.web.task.TaskEditForm","org.projectforge.web.task.TaskListPage","org.projectforge.web.task.TaskTreeBuilder","org.projectforge.web.teamcal.event.TeamAttendeesPanel"],"usedInFiles":["./projectforge-business/src/main/resources/mail/orderChangeNotification.html","./projectforge-wicket/src/main/java/org/projectforge/web/teamcal/event/TeamAttendeesPanel.html"]}, {"i18nKey":"stop","bundleName":"I18nResources","translation":"Stop","translationDE":"Beenden","usedInClasses":["org.projectforge.rest.multiselect.AbstractMultiSelectedPage"],"usedInFiles":[]}, {"i18nKey":"sum","bundleName":"I18nResources","translation":"Sum","translationDE":"Summe","usedInClasses":["org.projectforge.business.fibu.datev.EmployeeSalaryExportDao","org.projectforge.web.fibu.MonthlyEmployeeReportPage","org.projectforge.web.humanresources.HRListPage"],"usedInFiles":["./projectforge-wicket/src/main/java/org/projectforge/web/fibu/MonthlyEmployeeReportPage.html"]}, {"i18nKey":"system.admin.adminLogViewer.title","bundleName":"I18nResources","translation":"Admin log viewer","translationDE":"Adminprotokoll einsehen","usedInClasses":["org.projectforge.rest.admin.LogViewerPageRest"],"usedInFiles":[]}, - {"i18nKey":"system.admin.alertMessage.copyAndPaste.text","bundleName":"I18nResources","translation":"Attention: ProjectForge will not be available at 1 pm for approx. 5 minutes due to maintenance reasons. The new version {0} will be released.","translationDE":"Achtung: ProjectForge ist um 13:00 Uhr für ca. 5 Minuten aufgrund von Wartungsarbeiten nicht erreichbar! Es wird das neue Release {0} eingespielt.","usedInClasses":["org.projectforge.web.admin.AdminForm"],"usedInFiles":[]}, + {"i18nKey":"system.admin.alertMessage.copyAndPaste.text","bundleName":"I18nResources","translation":"Attention: ProjectForge will not be available at 1 pm for approx. 5 minutes due to maintenance reasons. The new version {0} will be released.","translationDE":"Achtung: ProjectForge ist um 13:00 Uhr für ca. 5 Minuten aufgrund von Wartungsarbeiten nicht erreichbar! Es wird das neue Release {0} eingespielt.","usedInClasses":["org.projectforge.web.admin.AdminForm"],"usedInFiles":[]}, {"i18nKey":"system.admin.alertMessage.copyAndPaste.title","bundleName":"I18nResources","translation":"For copy & paste","translationDE":"For copy & paste","usedInClasses":["org.projectforge.web.admin.AdminForm"],"usedInFiles":[]}, {"i18nKey":"system.admin.button.checkI18nProperties","bundleName":"I18nResources","translation":"Check i18n properties","translationDE":"Check i18n properties","usedInClasses":["org.projectforge.web.admin.AdminPage"],"usedInFiles":[]}, {"i18nKey":"system.admin.button.checkI18nProperties.tooltip","bundleName":"I18nResources","translation":"Check i18n properties for detecting missing translations in different languages.","translationDE":"Check i18n properties for detecting missing translations in different languages.","usedInClasses":["org.projectforge.web.admin.AdminPage"],"usedInFiles":[]}, @@ -2204,28 +2258,28 @@ {"i18nKey":"system.pluginAdmin.title","bundleName":"I18nResources","translation":"Plugins","translationDE":"Plugins","usedInClasses":["org.projectforge.web.admin.PluginListPage"],"usedInFiles":[]}, {"i18nKey":"system.statistics.databasePool","bundleName":"I18nResources","translation":"Data base pool","translationDE":"Data base pool","usedInClasses":["org.projectforge.business.admin.DatabaseStatisticsBuilder"],"usedInFiles":[]}, {"i18nKey":"system.statistics.title","bundleName":"I18nResources","translation":"System statistics","translationDE":"Systemstatistiken","usedInClasses":["org.projectforge.rest.SystemStatisticPageRest"],"usedInFiles":[]}, - {"i18nKey":"system.statistics.totalNumberOfHistoryEntries","bundleName":"I18nResources","translation":"Total number of history entries","translationDE":"Gesamtzahl aller Historierungseinträge","usedInClasses":["org.projectforge.business.admin.DatabaseStatisticsBuilder"],"usedInFiles":[]}, + {"i18nKey":"system.statistics.totalNumberOfHistoryEntries","bundleName":"I18nResources","translation":"Total number of history entries","translationDE":"Gesamtzahl aller Historierungseinträge","usedInClasses":["org.projectforge.business.admin.DatabaseStatisticsBuilder"],"usedInFiles":[]}, {"i18nKey":"system.statistics.totalNumberOfTasks","bundleName":"I18nResources","translation":"Total number of structure elements","translationDE":"Gesamtzahl aller Strukturelemente","usedInClasses":["org.projectforge.business.admin.DatabaseStatisticsBuilder"],"usedInFiles":[]}, {"i18nKey":"system.statistics.totalNumberOfTimesheets","bundleName":"I18nResources","translation":"Total number of time sheets","translationDE":"Gesamtzahl der Zeitberichte","usedInClasses":["org.projectforge.business.admin.DatabaseStatisticsBuilder"],"usedInFiles":[]}, {"i18nKey":"system.statistics.totalNumberOfUsers","bundleName":"I18nResources","translation":"Total number of users","translationDE":"Gesamtzahl der Benutzer:innen","usedInClasses":["org.projectforge.business.admin.DatabaseStatisticsBuilder"],"usedInFiles":[]}, - {"i18nKey":"system.statistics.totalTimesheetDurations","bundleName":"I18nResources","translation":"Total duration over all time sheets [days]","translationDE":"Gesamtdauer über alle Zeitberichte [Tage]","usedInClasses":["org.projectforge.business.admin.DatabaseStatisticsBuilder"],"usedInFiles":[]}, + {"i18nKey":"system.statistics.totalTimesheetDurations","bundleName":"I18nResources","translation":"Total duration over all time sheets [days]","translationDE":"Gesamtdauer über alle Zeitberichte [Tage]","usedInClasses":["org.projectforge.business.admin.DatabaseStatisticsBuilder"],"usedInFiles":[]}, {"i18nKey":"table.showing","bundleName":"I18nResources","translation":"Showing","translationDE":"Anzeige","usedInClasses":["org.projectforge.rest.core.AbstractPagesRest"],"usedInFiles":[]}, {"i18nKey":"task","bundleName":"I18nResources","translation":"Structure element","translationDE":"Strukturelement","usedInClasses":["org.projectforge.business.fibu.OrderExport","org.projectforge.business.fibu.ProjektDO","org.projectforge.business.gantt.GanttChartDO","org.projectforge.business.scripting.ScriptParameterType","org.projectforge.business.task.LegacyTaskFavorite","org.projectforge.business.task.TaskFavoritesService","org.projectforge.business.task.TaskNode","org.projectforge.business.timesheet.TimesheetDO","org.projectforge.business.user.UserPrefDOXmlDumpHook","org.projectforge.framework.access.AccessDao","org.projectforge.framework.access.AccessException","org.projectforge.framework.access.GroupTaskAccessDO","org.projectforge.framework.persistence.DaoConst","org.projectforge.framework.persistence.database.DatabaseInitTestDataService","org.projectforge.framework.persistence.user.entities.UserPrefXmlBeforePersistListener","org.projectforge.model.rest.RestPaths","org.projectforge.plugins.todo.ToDoDO","org.projectforge.plugins.todo.ToDoEditForm","org.projectforge.plugins.todo.ToDoListForm","org.projectforge.plugins.todo.ToDoListPage","org.projectforge.plugins.todo.ToDoType","org.projectforge.plugins.todo.rest.ToDoPagesRest","org.projectforge.registry.Registry","org.projectforge.renderer.custom.MicromataFormatter","org.projectforge.rest.GroupAccessPagesRest","org.projectforge.rest.TimesheetFavoritesRest","org.projectforge.rest.TimesheetMultiSelectedPageRest","org.projectforge.rest.TimesheetPagesRest","org.projectforge.rest.calendar.TimesheetEventsProvider","org.projectforge.rest.config.JacksonConfiguration","org.projectforge.rest.fibu.ProjectPagesRest","org.projectforge.rest.scripting.AbstractScriptExecutePageRest","org.projectforge.rest.task.TaskServicesRest","org.projectforge.rest.task.TaskWizardPageRest","org.projectforge.ui.LayoutUtils","org.projectforge.web.access.AccessEditForm","org.projectforge.web.access.AccessListForm","org.projectforge.web.access.AccessListPage","org.projectforge.web.admin.TaskWizardForm","org.projectforge.web.calendar.TimesheetEventsProvider","org.projectforge.web.fibu.AuftragEditForm","org.projectforge.web.fibu.MonthlyEmployeeReportPage","org.projectforge.web.fibu.ProjektEditForm","org.projectforge.web.fibu.ProjektListPage","org.projectforge.web.gantt.GanttChartEditForm","org.projectforge.web.gantt.GanttChartEditPage","org.projectforge.web.gantt.GanttChartEditTreeTablePanel","org.projectforge.web.gantt.GanttChartListPage","org.projectforge.web.task.TaskEditPage","org.projectforge.web.task.TaskListPage","org.projectforge.web.task.TaskTreeBuilder","org.projectforge.web.timesheet.TimesheetEditForm","org.projectforge.web.timesheet.TimesheetEditSelectRecentDialogPanel","org.projectforge.web.timesheet.TimesheetListForm","org.projectforge.web.timesheet.TimesheetListPage"],"usedInFiles":["./projectforge-business/src/main/resources/mail/todoChangeNotification.html"]}, {"i18nKey":"task.assignedUser","bundleName":"I18nResources","translation":"Responsible user","translationDE":"Verantwortliche:r","usedInClasses":["org.projectforge.business.task.TaskDO","org.projectforge.web.task.TaskEditForm","org.projectforge.web.task.TaskListPage","org.projectforge.web.task.TaskTreeBuilder"],"usedInFiles":[]}, {"i18nKey":"task.consumption","bundleName":"I18nResources","translation":"Consumption","translationDE":"Verbrauch","usedInClasses":["org.projectforge.rest.TimesheetPagesRest","org.projectforge.rest.task.TaskServicesRest","org.projectforge.web.task.TaskListPage","org.projectforge.web.task.TaskTreeBuilder","org.projectforge.web.timesheet.TimesheetEditForm"],"usedInFiles":[]}, - {"i18nKey":"task.deleted","bundleName":"I18nResources","translation":"deleted","translationDE":"gelöscht","usedInClasses":["org.projectforge.business.task.formatter.WicketTaskFormatter"],"usedInFiles":[]}, - {"i18nKey":"task.edit.maxHoursIngoredDueToAssignedOrders","bundleName":"I18nResources","translation":"This value will may-be be ignored - There are order positions with given person days assigned to this node or any subnode. A zero value suppress displaying a maximum value in the consumption bar.","translationDE":"Diese Angabe wird ggf. ignoriert. - Zu diesem Strukturelement oder einem Strukturunterelement wurden Aufträge zugeordnet. Die Angabe einer Null verhindert jedoch die Anzeige des Maximalwerts in den Verbrauchsbalken.","usedInClasses":["org.projectforge.web.task.TaskEditForm"],"usedInFiles":[]}, - {"i18nKey":"task.error.couldNotDeleteRootTask","bundleName":"I18nResources","translation":"Root structure element can't be deleted!","translationDE":"Das Wurzelstrukturelement kann nicht gelöscht werden!","usedInClasses":["org.projectforge.business.task.TaskDao"],"usedInFiles":[]}, - {"i18nKey":"task.error.cyclicReference","bundleName":"I18nResources","translation":"Cyclic structure element path detected because parent structure element is the element itself or a descendant element.","translationDE":"Zyklische Strukturhierarchie entdeckt, weil ein Strukturelement sich selbst oder ein Strukturunterelement als übergeordnetes Strukturelement besitzt.","usedInClasses":["org.projectforge.business.task.TaskDao"],"usedInFiles":[]}, - {"i18nKey":"task.error.duplicateChildTasks","bundleName":"I18nResources","translation":"Duplicate structure element titles for sister elements detected.","translationDE":"Strukturelemente müssen als Unterelemente unterschiedliche Titel haben.","usedInClasses":["org.projectforge.business.task.TaskDao"],"usedInFiles":[]}, + {"i18nKey":"task.deleted","bundleName":"I18nResources","translation":"deleted","translationDE":"gelöscht","usedInClasses":["org.projectforge.business.task.formatter.WicketTaskFormatter"],"usedInFiles":[]}, + {"i18nKey":"task.edit.maxHoursIngoredDueToAssignedOrders","bundleName":"I18nResources","translation":"This value will may-be be ignored - There are order positions with given person days assigned to this node or any subnode. A zero value suppress displaying a maximum value in the consumption bar.","translationDE":"Diese Angabe wird ggf. ignoriert. - Zu diesem Strukturelement oder einem Strukturunterelement wurden Aufträge zugeordnet. Die Angabe einer Null verhindert jedoch die Anzeige des Maximalwerts in den Verbrauchsbalken.","usedInClasses":["org.projectforge.web.task.TaskEditForm"],"usedInFiles":[]}, + {"i18nKey":"task.error.couldNotDeleteRootTask","bundleName":"I18nResources","translation":"Root structure element can't be deleted!","translationDE":"Das Wurzelstrukturelement kann nicht gelöscht werden!","usedInClasses":["org.projectforge.business.task.TaskDao"],"usedInFiles":[]}, + {"i18nKey":"task.error.cyclicReference","bundleName":"I18nResources","translation":"Cyclic structure element path detected because parent structure element is the element itself or a descendant element.","translationDE":"Zyklische Strukturhierarchie entdeckt, weil ein Strukturelement sich selbst oder ein Strukturunterelement als übergeordnetes Strukturelement besitzt.","usedInClasses":["org.projectforge.business.task.TaskDao"],"usedInFiles":[]}, + {"i18nKey":"task.error.duplicateChildTasks","bundleName":"I18nResources","translation":"Duplicate structure element titles for sister elements detected.","translationDE":"Strukturelemente müssen als Unterelemente unterschiedliche Titel haben.","usedInClasses":["org.projectforge.business.task.TaskDao"],"usedInFiles":[]}, {"i18nKey":"task.error.kost2Readonly","bundleName":"I18nResources","translation":"Kost2 is read-only for non accounting staff members.","translationDE":"Eine Kost2-Zuordnung kann nur durch die Buchhaltung vorgenommen werden.","usedInClasses":["org.projectforge.business.task.TaskDao"],"usedInFiles":[]}, - {"i18nKey":"task.error.parentTaskNotFound","bundleName":"I18nResources","translation":"Parent structure element is required but not found.","translationDE":"Übergeordnetes Strukturelement wird benötigt, konnte aber nicht gefunden werden.","usedInClasses":["org.projectforge.business.task.TaskDao"],"usedInFiles":[]}, - {"i18nKey":"task.error.parentTaskNotGiven","bundleName":"I18nResources","translation":"Parent structure element is required but not given.","translationDE":"Übergeordnetes Strukturelement muss angegeben sein.","usedInClasses":["org.projectforge.business.task.TaskDao"],"usedInFiles":[]}, + {"i18nKey":"task.error.parentTaskNotFound","bundleName":"I18nResources","translation":"Parent structure element is required but not found.","translationDE":"Übergeordnetes Strukturelement wird benötigt, konnte aber nicht gefunden werden.","usedInClasses":["org.projectforge.business.task.TaskDao"],"usedInFiles":[]}, + {"i18nKey":"task.error.parentTaskNotGiven","bundleName":"I18nResources","translation":"Parent structure element is required but not given.","translationDE":"Übergeordnetes Strukturelement muss angegeben sein.","usedInClasses":["org.projectforge.business.task.TaskDao"],"usedInFiles":[]}, {"i18nKey":"task.error.protectTimesheetsUntilReadonly","bundleName":"I18nResources","translation":"The time sheet protection is read-only for non accounting staff members.","translationDE":"Der Zeitberichtsschutz kann nur durch die Buchhaltung manipuliert werden.","usedInClasses":["org.projectforge.business.task.TaskDao"],"usedInFiles":[]}, - {"i18nKey":"task.error.protectionOfPrivacyReadonly","bundleName":"I18nResources","translation":"The flag privacy of protection is read-only for non accounting staff members.","translationDE":"Die Datenschutzoption kann nur durch die Buchhaltung geändert werden.","usedInClasses":["org.projectforge.business.task.TaskDao"],"usedInFiles":[]}, - {"i18nKey":"task.error.timesheetBookingStatus2Readonly","bundleName":"I18nResources","translation":"The status of time sheet booking is read-only for non accouning staff members and non project managers.","translationDE":"Der Status für Zeitberichtsbuchungen kann nur durch die Buchhaltung oder Projektmanager manipuliert werden.","usedInClasses":["org.projectforge.business.task.TaskDao"],"usedInFiles":[]}, + {"i18nKey":"task.error.protectionOfPrivacyReadonly","bundleName":"I18nResources","translation":"The flag privacy of protection is read-only for non accounting staff members.","translationDE":"Die Datenschutzoption kann nur durch die Buchhaltung geändert werden.","usedInClasses":["org.projectforge.business.task.TaskDao"],"usedInFiles":[]}, + {"i18nKey":"task.error.timesheetBookingStatus2Readonly","bundleName":"I18nResources","translation":"The status of time sheet booking is read-only for non accouning staff members and non project managers.","translationDE":"Der Status für Zeitberichtsbuchungen kann nur durch die Buchhaltung oder Projektmanager manipuliert werden.","usedInClasses":["org.projectforge.business.task.TaskDao"],"usedInFiles":[]}, {"i18nKey":"task.favorite.new","bundleName":"I18nResources","translation":"New favorite of structure element","translationDE":"Neuer Strukturelementfavorit","usedInClasses":["org.projectforge.ui.LayoutUtils"],"usedInFiles":[]}, - {"i18nKey":"task.favorite.new.tooltip","bundleName":"I18nResources","translation":"You may save the chosen structure element as favorite here.","translationDE":"Das aktuell ausgewählte Strukturelement kann hier unter einem Namen als Favorit gespeichert werden.","usedInClasses":["org.projectforge.ui.LayoutUtils"],"usedInFiles":[]}, + {"i18nKey":"task.favorite.new.tooltip","bundleName":"I18nResources","translation":"You may save the chosen structure element as favorite here.","translationDE":"Das aktuell ausgewählte Strukturelement kann hier unter einem Namen als Favorit gespeichert werden.","usedInClasses":["org.projectforge.ui.LayoutUtils"],"usedInFiles":[]}, {"i18nKey":"task.favorites.tooltip","bundleName":"I18nResources","translation":"Organize here your structure elements as favorites.","translationDE":"Verwaltung von Strukturelementen als Favoriten","usedInClasses":["org.projectforge.ui.LayoutUtils"],"usedInFiles":[]}, {"i18nKey":"task.gantt.settings","bundleName":"I18nResources","translation":"Gantt settings","translationDE":"Ganttwerte","usedInClasses":["org.projectforge.web.task.TaskEditForm"],"usedInFiles":[]}, {"i18nKey":"task.kost2list.blackList","bundleName":"I18nResources","translation":"Black list","translationDE":"Negativliste","usedInClasses":["org.projectforge.web.task.TaskEditForm"],"usedInFiles":[]}, @@ -2237,19 +2291,19 @@ {"i18nKey":"task.menu.showAccessRights","bundleName":"I18nResources","translation":"Access rights","translationDE":"Zugriffsrechte","usedInClasses":["org.projectforge.web.task.TaskEditPage"],"usedInFiles":[]}, {"i18nKey":"task.menu.showTimesheets","bundleName":"I18nResources","translation":"Show time sheets","translationDE":"Zeitberichte anzeigen","usedInClasses":["org.projectforge.web.task.TaskEditPage"],"usedInFiles":[]}, {"i18nKey":"task.onlyBillable","bundleName":"I18nResources","translation":"only billable elements","translationDE":"nur fakturierbare Elemente","usedInClasses":["org.projectforge.web.timesheet.TimesheetListForm"],"usedInFiles":[]}, - {"i18nKey":"task.parentTask","bundleName":"I18nResources","translation":"Parent structure element","translationDE":"Übergeordnetes Strukturelement","usedInClasses":["org.projectforge.business.task.TaskDO","org.projectforge.web.task.TaskEditForm"],"usedInFiles":[]}, + {"i18nKey":"task.parentTask","bundleName":"I18nResources","translation":"Parent structure element","translationDE":"Übergeordnetes Strukturelement","usedInClasses":["org.projectforge.business.task.TaskDO","org.projectforge.web.task.TaskEditForm"],"usedInFiles":[]}, {"i18nKey":"task.path","bundleName":"I18nResources","translation":"Structure element path","translationDE":"Strukturhierarchie","usedInClasses":["org.projectforge.business.timesheet.TimesheetExport"],"usedInFiles":[]}, - {"i18nKey":"task.path.pleaseSelectTask","bundleName":"I18nResources","translation":"Please select structure element","translationDE":"Bitte Strukturelement wählen","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"task.path.pleaseSelectTask","bundleName":"I18nResources","translation":"Please select structure element","translationDE":"Bitte Strukturelement wählen","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"task.path.rootTask","bundleName":"I18nResources","translation":"Top level","translationDE":"Toplevel","usedInClasses":["org.projectforge.business.task.formatter.WicketTaskFormatter","org.projectforge.web.task.TaskSelectAutoCompleteFormComponent"],"usedInFiles":[]}, {"i18nKey":"task.progress","bundleName":"I18nResources","translation":"Progress","translationDE":"Fortschritt","usedInClasses":["org.projectforge.business.task.TaskDO","org.projectforge.web.gantt.GanttChartEditTreeTablePanel","org.projectforge.web.task.TaskEditForm"],"usedInFiles":[]}, {"i18nKey":"task.protectTimesheetsUntil","bundleName":"I18nResources","translation":"Protect time sheets until","translationDE":"Zeitberichtsschutz bis","usedInClasses":["org.projectforge.business.task.TaskDO","org.projectforge.web.task.TaskEditForm","org.projectforge.web.task.TaskTreeBuilder"],"usedInFiles":[]}, {"i18nKey":"task.protectTimesheetsUntil.short","bundleName":"I18nResources","translation":"Protect until","translationDE":"Schutz bis","usedInClasses":["org.projectforge.web.task.TaskListPage","org.projectforge.web.task.TaskTreeBuilder"],"usedInFiles":[]}, {"i18nKey":"task.protectionOfPrivacy","bundleName":"I18nResources","translation":"Protection of privacy","translationDE":"Datenschutz","usedInClasses":["org.projectforge.business.task.TaskDO","org.projectforge.web.task.TaskEditForm"],"usedInFiles":[]}, - {"i18nKey":"task.protectionOfPrivacy.tooltip","bundleName":"I18nResources","translation":"If checked then users don't have access to the time sheets of other users assigned to this structure element or any sub element.","translationDE":"Wenn diese Option gewählt wird, dann kann nicht auf die Zeitberichte anderer Benutzer:innen unterhalb dieses Tasks zugegriffen werden.","usedInClasses":["org.projectforge.web.task.TaskEditForm"],"usedInFiles":[]}, + {"i18nKey":"task.protectionOfPrivacy.tooltip","bundleName":"I18nResources","translation":"If checked then users don't have access to the time sheets of other users assigned to this structure element or any sub element.","translationDE":"Wenn diese Option gewählt wird, dann kann nicht auf die Zeitberichte anderer Benutzer:innen unterhalb dieses Tasks zugegriffen werden.","usedInClasses":["org.projectforge.web.task.TaskEditForm"],"usedInFiles":[]}, {"i18nKey":"task.recursive","bundleName":"I18nResources","translation":"incl. all structure sub elements","translationDE":"inkl. aller Strukturunterelemente","usedInClasses":["org.projectforge.web.timesheet.TimesheetListForm"],"usedInFiles":[]}, {"i18nKey":"task.reference","bundleName":"I18nResources","translation":"Reference","translationDE":"Referenz","usedInClasses":["org.projectforge.business.task.TaskDO","org.projectforge.web.task.TaskEditForm","org.projectforge.web.task.TaskListPage","org.projectforge.web.task.TaskTreeBuilder"],"usedInFiles":[]}, {"i18nKey":"task.selectPanel.displayTask.tooltip","bundleName":"I18nResources","translation":"Display structure element.","translationDE":"Strukturelement anzeigen.","usedInClasses":["org.projectforge.web.task.TaskSelectPanel"],"usedInFiles":[]}, - {"i18nKey":"task.selectPanel.info","bundleName":"I18nResources","translation":"You may select folders by clicking in other columns of the row than the first.","translationDE":"Ordnerelement können ausgewählt werden, indem in eine andere Spalte geklickt wird (außer der ersten).","usedInClasses":["org.projectforge.rest.task.TaskServicesRest"],"usedInFiles":[]}, + {"i18nKey":"task.selectPanel.info","bundleName":"I18nResources","translation":"You may select folders by clicking in other columns of the row than the first.","translationDE":"Ordnerelement können ausgewählt werden, indem in eine andere Spalte geklickt wird (außer der ersten).","usedInClasses":["org.projectforge.rest.task.TaskServicesRest"],"usedInFiles":[]}, {"i18nKey":"task.selectPanel.noTasksFound","bundleName":"I18nResources","translation":"No structure elements found.","translationDE":"Keine Strukturelemente gefunden.","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"task.selectPanel.selectAncestorTask.tooltip","bundleName":"I18nResources","translation":"Replace task by this ancestor structure element.","translationDE":"Strukturelement durch dieses Strukturoberelement ersetzen.","usedInClasses":["org.projectforge.web.task.TaskSelectPanel"],"usedInFiles":[]}, {"i18nKey":"task.status","bundleName":"I18nResources","translation":"Status of structure element","translationDE":"Status des Strukturelements","usedInClasses":[],"usedInFiles":[]}, @@ -2260,39 +2314,39 @@ {"i18nKey":"task.timesheetBooking","bundleName":"I18nResources","translation":"Time sheet booking","translationDE":"Zeitbuchungen","usedInClasses":["org.projectforge.business.task.TaskDO","org.projectforge.web.task.TaskEditForm"],"usedInFiles":[]}, {"i18nKey":"task.timesheetBooking.inherit","bundleName":"I18nResources","translation":"inherit","translationDE":"vererbt","usedInClasses":["org.projectforge.common.task.TimesheetBookingStatus"],"usedInFiles":[]}, {"i18nKey":"task.timesheetBooking.noBooking","bundleName":"I18nResources","translation":"no booking","translationDE":"keine Buchungen","usedInClasses":["org.projectforge.common.task.TimesheetBookingStatus"],"usedInFiles":[]}, - {"i18nKey":"task.timesheetBooking.onlyLeafs","bundleName":"I18nResources","translation":"only leaf nodes","translationDE":"nur Strukturelementblätter","usedInClasses":["org.projectforge.common.task.TimesheetBookingStatus"],"usedInFiles":[]}, - {"i18nKey":"task.timesheetBooking.opened","bundleName":"I18nResources","translation":"opened","translationDE":"geöffnet","usedInClasses":["org.projectforge.common.task.TimesheetBookingStatus"],"usedInFiles":[]}, + {"i18nKey":"task.timesheetBooking.onlyLeafs","bundleName":"I18nResources","translation":"only leaf nodes","translationDE":"nur Strukturelementblätter","usedInClasses":["org.projectforge.common.task.TimesheetBookingStatus"],"usedInFiles":[]}, + {"i18nKey":"task.timesheetBooking.opened","bundleName":"I18nResources","translation":"opened","translationDE":"geöffnet","usedInClasses":["org.projectforge.common.task.TimesheetBookingStatus"],"usedInFiles":[]}, {"i18nKey":"task.timesheetBooking.treeClosed","bundleName":"I18nResources","translation":"completely closed","translationDE":"komplett geschlossen","usedInClasses":["org.projectforge.common.task.TimesheetBookingStatus"],"usedInFiles":[]}, {"i18nKey":"task.title","bundleName":"I18nResources","translation":"Title","translationDE":"Titel","usedInClasses":["org.projectforge.business.gantt.GanttChartDao","org.projectforge.business.task.TaskDO","org.projectforge.business.timesheet.TimesheetExport","org.projectforge.framework.access.AccessDao","org.projectforge.plugins.todo.ToDoDao","org.projectforge.plugins.todo.ToDoListPage","org.projectforge.rest.GroupAccessPagesRest","org.projectforge.rest.task.TaskPagesRest","org.projectforge.web.access.AccessListPage","org.projectforge.web.fibu.ProjektListPage","org.projectforge.web.gantt.GanttChartListPage","org.projectforge.web.task.TaskEditForm","org.projectforge.web.task.TaskTreeBuilder","org.projectforge.web.timesheet.TimesheetEditSelectRecentDialogPanel","org.projectforge.web.timesheet.TimesheetListPage"],"usedInFiles":[]}, {"i18nKey":"task.title.add","bundleName":"I18nResources","translation":"Add new structure element","translationDE":"Neues Strukturelement","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"task.title.edit","bundleName":"I18nResources","translation":"Edit structure element","translationDE":"Strukturelement bearbeiten","usedInClasses":["org.projectforge.web.gantt.GanttChartEditTreeTablePanel","org.projectforge.web.task.TaskEditPage","org.projectforge.web.task.TaskListPage"],"usedInFiles":[]}, {"i18nKey":"task.title.heading","bundleName":"I18nResources","translation":"Structure elements","translationDE":"Strukturelemente","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"task.title.list","bundleName":"I18nResources","translation":"Structure elements","translationDE":"Strukturelemente","usedInClasses":["org.projectforge.web.task.TaskEditPage","org.projectforge.web.task.TaskListPage"],"usedInFiles":[]}, - {"i18nKey":"task.title.list.select","bundleName":"I18nResources","translation":"Select structure element","translationDE":"Strukturelement auswählen","usedInClasses":["org.projectforge.ui.LayoutUtils","org.projectforge.web.task.TaskEditPage","org.projectforge.web.task.TaskListPage"],"usedInFiles":[]}, + {"i18nKey":"task.title.list.select","bundleName":"I18nResources","translation":"Select structure element","translationDE":"Strukturelement auswählen","usedInClasses":["org.projectforge.ui.LayoutUtils","org.projectforge.web.task.TaskEditPage","org.projectforge.web.task.TaskListPage"],"usedInFiles":[]}, {"i18nKey":"task.tree.close","bundleName":"I18nResources","translation":"Close structure element","translationDE":"Strukturelement zuklappen","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"task.tree.explore","bundleName":"I18nResources","translation":"Close/open all structure sub elements","translationDE":"Alle Strukturelemente auf-/zuklappen","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"task.tree.info","bundleName":"I18nResources","translation":"You may expand or collapse structure elements with structure sub elements by clicking the folder icons or the title of the element, you may select any element by clicking the row at any other position.","translationDE":"Strukturelemente können auf- und zugeklappt werden, indem auf die Ordnersymbole oder Titel des gewünschten Strukturelements geklickt wird. Zur Auswahl eines Strukturelements kann jede andere Stelle innerhalb der Zeile angeklickt werden.","usedInClasses":["org.projectforge.web.task.TaskTreePage"],"usedInFiles":[]}, + {"i18nKey":"task.tree.info","bundleName":"I18nResources","translation":"You may expand or collapse structure elements with structure sub elements by clicking the folder icons or the title of the element, you may select any element by clicking the row at any other position.","translationDE":"Strukturelemente können auf- und zugeklappt werden, indem auf die Ordnersymbole oder Titel des gewünschten Strukturelements geklickt wird. Zur Auswahl eines Strukturelements kann jede andere Stelle innerhalb der Zeile angeklickt werden.","usedInClasses":["org.projectforge.web.task.TaskTreePage"],"usedInFiles":[]}, {"i18nKey":"task.tree.open","bundleName":"I18nResources","translation":"Open structure element","translationDE":"Strukturelement aufklappen","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"task.tree.perspective","bundleName":"I18nResources","translation":"structure tree","translationDE":"Baumansicht","usedInClasses":["org.projectforge.web.task.TaskListForm"],"usedInFiles":[]}, {"i18nKey":"task.tree.rootNode","bundleName":"I18nResources","translation":"Root node","translationDE":"Wurzelstrukturelement","usedInClasses":["org.projectforge.ui.LayoutUtils"],"usedInFiles":[]}, {"i18nKey":"task.tree.title","bundleName":"I18nResources","translation":"structure tree","translationDE":"Strukturbaum","usedInClasses":["org.projectforge.web.task.TaskTreePage"],"usedInFiles":[]}, - {"i18nKey":"task.tree.title.select","bundleName":"I18nResources","translation":"Select structure element","translationDE":"Strukturelement auswählen","usedInClasses":["org.projectforge.web.task.TaskTreePage"],"usedInFiles":[]}, + {"i18nKey":"task.tree.title.select","bundleName":"I18nResources","translation":"Select structure element","translationDE":"Strukturelement auswählen","usedInClasses":["org.projectforge.web.task.TaskTreePage"],"usedInFiles":[]}, {"i18nKey":"task.wizard.action","bundleName":"I18nResources","translation":"Action","translationDE":"Aktion","usedInClasses":["org.projectforge.rest.task.TaskWizardPageRest","org.projectforge.web.admin.TaskWizardForm"],"usedInFiles":[]}, - {"i18nKey":"task.wizard.action.noactionRequired","bundleName":"I18nResources","translation":"Nothing will be further done because you haven't defined any group, therefore no access rights will be created.","translationDE":"Keine Aktion notwendig, da keine Gruppen ausgewählt wurden. Demnach müssen keine Zugriffsrechte angelegt werden.","usedInClasses":["org.projectforge.rest.task.TaskWizardPageRest","org.projectforge.web.admin.TaskWizardForm"],"usedInFiles":[]}, - {"i18nKey":"task.wizard.action.taskAndgroupsGiven","bundleName":"I18nResources","translation":"The required access rights of the group(s) defined above will be set if you click on the create button. You can change these access rights via the menu item access management whenever needed.","translationDE":"Die notwendigen Zugriffsrechte für die oben angegebene(n) Gruppe(n) werden angelegt, nachdem der Anlegen-Button gedrückt wird. Diese können Sie jederzeit unter dem Menüpunkt Zugriffsverwaltung einsehen und ändern.","usedInClasses":["org.projectforge.web.admin.TaskWizardForm"],"usedInFiles":[]}, + {"i18nKey":"task.wizard.action.noactionRequired","bundleName":"I18nResources","translation":"Nothing will be further done because you haven't defined any group, therefore no access rights will be created.","translationDE":"Keine Aktion notwendig, da keine Gruppen ausgewählt wurden. Demnach müssen keine Zugriffsrechte angelegt werden.","usedInClasses":["org.projectforge.rest.task.TaskWizardPageRest","org.projectforge.web.admin.TaskWizardForm"],"usedInFiles":[]}, + {"i18nKey":"task.wizard.action.taskAndgroupsGiven","bundleName":"I18nResources","translation":"The required access rights of the group(s) defined above will be set if you click on the create button. You can change these access rights via the menu item access management whenever needed.","translationDE":"Die notwendigen Zugriffsrechte für die oben angegebene(n) Gruppe(n) werden angelegt, nachdem der Anlegen-Button gedrückt wird. Diese können Sie jederzeit unter dem Menüpunkt Zugriffsverwaltung einsehen und ändern.","usedInClasses":["org.projectforge.web.admin.TaskWizardForm"],"usedInFiles":[]}, {"i18nKey":"task.wizard.button.createGroup","bundleName":"I18nResources","translation":"create group","translationDE":"Gruppe anlegen","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"task.wizard.button.createGroup.tooltip","bundleName":"I18nResources","translation":"You can choose an existing group or create a new group by using this button.","translationDE":"Sie können eine existierende Benutzergruppe wählen oder eine neue durch Anklicken dieses Buttons anlegen.","usedInClasses":["org.projectforge.web.admin.TaskWizardForm"],"usedInFiles":[]}, + {"i18nKey":"task.wizard.button.createGroup.tooltip","bundleName":"I18nResources","translation":"You can choose an existing group or create a new group by using this button.","translationDE":"Sie können eine existierende Benutzergruppe wählen oder eine neue durch Anklicken dieses Buttons anlegen.","usedInClasses":["org.projectforge.web.admin.TaskWizardForm"],"usedInFiles":[]}, {"i18nKey":"task.wizard.button.createTask","bundleName":"I18nResources","translation":"create structure element","translationDE":"Strukturelement anlegen","usedInClasses":["org.projectforge.web.admin.TaskWizardForm"],"usedInFiles":[]}, - {"i18nKey":"task.wizard.button.createTask.tooltip","bundleName":"I18nResources","translation":"You can choose an existing structure element or create a new element by using this button.","translationDE":"Sie können ein existierendes Strukturelement wählen oder ein neues durch Anklicken dieses Buttons anlegen.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"task.wizard.button.createTask.tooltip","bundleName":"I18nResources","translation":"You can choose an existing structure element or create a new element by using this button.","translationDE":"Sie können ein existierendes Strukturelement wählen oder ein neues durch Anklicken dieses Buttons anlegen.","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"task.wizard.finish","bundleName":"I18nResources","translation":"Finish","translationDE":"Fertig stellen","usedInClasses":["org.projectforge.rest.task.TaskWizardPageRest","org.projectforge.web.admin.TaskWizardForm"],"usedInFiles":[]}, - {"i18nKey":"task.wizard.intro","bundleName":"I18nResources","translation":"Use this wizard to add a new structure element which represents e. g. a project with assigned groups and users. The assigned users will have access rights to this structure element (e. g. for booking time sheet etc.).","translationDE":"Benutzen Sie diesen Assistenten, um ein neues Strukturelement anzulegen. Dieses Strukturelement kann ein Projekt o. ä. repräsentieren auf welche Nutzer und Gruppen Zugriff haben sollen (z. B. für Zeitberichtserfassungen).","usedInClasses":["org.projectforge.rest.task.TaskWizardPageRest","org.projectforge.web.admin.TaskWizardForm"],"usedInFiles":[]}, + {"i18nKey":"task.wizard.intro","bundleName":"I18nResources","translation":"Use this wizard to add a new structure element which represents e. g. a project with assigned groups and users. The assigned users will have access rights to this structure element (e. g. for booking time sheet etc.).","translationDE":"Benutzen Sie diesen Assistenten, um ein neues Strukturelement anzulegen. Dieses Strukturelement kann ein Projekt o. ä. repräsentieren auf welche Nutzer und Gruppen Zugriff haben sollen (z. B. für Zeitberichtserfassungen).","usedInClasses":["org.projectforge.rest.task.TaskWizardPageRest","org.projectforge.web.admin.TaskWizardForm"],"usedInFiles":[]}, {"i18nKey":"task.wizard.managerGroup","bundleName":"I18nResources","translation":"Managing users (optional)","translationDE":"Teammanager:in (optional)","usedInClasses":["org.projectforge.rest.task.TaskWizardPageRest"],"usedInFiles":[]}, {"i18nKey":"task.wizard.managerGroup.groupNameSuffix","bundleName":"I18nResources","translation":"managers","translationDE":"Projektmanagement","usedInClasses":["org.projectforge.web.admin.TaskWizardForm"],"usedInFiles":[]}, - {"i18nKey":"task.wizard.managerGroup.intro","bundleName":"I18nResources","translation":"The managing users have more rights. They're able to modifiy the time sheets of other team members. If they're also project managers they've the right to do HR planning etc.","translationDE":"Die Teammanager:innen haben mehr Rechte, so dürfen sie beispielsweise die Zeitberichte anderer Nutzer ändern. Wenn sie die Rolle Projektmanagement (Gruppe PF_MANAGER) haben, dürfen sie auch HR-Planungen vornehmen.","usedInClasses":["org.projectforge.rest.task.TaskWizardPageRest"],"usedInFiles":[]}, + {"i18nKey":"task.wizard.managerGroup.intro","bundleName":"I18nResources","translation":"The managing users have more rights. They're able to modifiy the time sheets of other team members. If they're also project managers they've the right to do HR planning etc.","translationDE":"Die Teammanager:innen haben mehr Rechte, so dürfen sie beispielsweise die Zeitberichte anderer Nutzer ändern. Wenn sie die Rolle Projektmanagement (Gruppe PF_MANAGER) haben, dürfen sie auch HR-Planungen vornehmen.","usedInClasses":["org.projectforge.rest.task.TaskWizardPageRest"],"usedInFiles":[]}, {"i18nKey":"task.wizard.pageTitle","bundleName":"I18nResources","translation":"Structure wizard","translationDE":"Strukturassistent","usedInClasses":["org.projectforge.rest.task.TaskWizardPageRest","org.projectforge.web.admin.TaskWizardPage"],"usedInFiles":[]}, - {"i18nKey":"task.wizard.task.intro","bundleName":"I18nResources","translation":"For this structure element the rights for the given groups will be configured: For all ancestor elements a minimal read access will be configured for the groups, but all other elements in the upper hierarchy will not be accessible without further grants.","translationDE":"Für dieses Strukturelement sollen die Rechte automatisch für die unten angegebenen Gruppen eingerichtet werden. Dabei wird für alle Oberelemente ein minimales Leserecht, so dass aus der Baumansicht dieses Element für die Gruppen eingesehen werden kann. Andere Elemente aus höheren Ebenen bleiben unsichtbar.","usedInClasses":["org.projectforge.rest.task.TaskWizardPageRest"],"usedInFiles":[]}, + {"i18nKey":"task.wizard.task.intro","bundleName":"I18nResources","translation":"For this structure element the rights for the given groups will be configured: For all ancestor elements a minimal read access will be configured for the groups, but all other elements in the upper hierarchy will not be accessible without further grants.","translationDE":"Für dieses Strukturelement sollen die Rechte automatisch für die unten angegebenen Gruppen eingerichtet werden. Dabei wird für alle Oberelemente ein minimales Leserecht, so dass aus der Baumansicht dieses Element für die Gruppen eingesehen werden kann. Andere Elemente aus höheren Ebenen bleiben unsichtbar.","usedInClasses":["org.projectforge.rest.task.TaskWizardPageRest"],"usedInFiles":[]}, {"i18nKey":"task.wizard.team","bundleName":"I18nResources","translation":"Team users (optional)","translationDE":"Team (optional)","usedInClasses":["org.projectforge.rest.task.TaskWizardPageRest"],"usedInFiles":[]}, - {"i18nKey":"task.wizard.team.intro","bundleName":"I18nResources","translation":"The team members are able to create and modify structure sub elements and to book their time sheets.","translationDE":"Die Teammitglieder haben das Recht, Strukturunterelemente anzulegen oder zu ändern, sowie eigene Zeitberichte zu erfassen.","usedInClasses":["org.projectforge.rest.task.TaskWizardPageRest"],"usedInFiles":[]}, + {"i18nKey":"task.wizard.team.intro","bundleName":"I18nResources","translation":"The team members are able to create and modify structure sub elements and to book their time sheets.","translationDE":"Die Teammitglieder haben das Recht, Strukturunterelemente anzulegen oder zu ändern, sowie eigene Zeitberichte zu erfassen.","usedInClasses":["org.projectforge.rest.task.TaskWizardPageRest"],"usedInFiles":[]}, {"i18nKey":"templates","bundleName":"I18nResources","translation":"Templates","translationDE":"Vorlagen","usedInClasses":["org.projectforge.plugins.todo.ToDoEditForm","org.projectforge.plugins.todo.ToDoListPage","org.projectforge.web.teamcal.event.TeamEventListForm"],"usedInFiles":[]}, {"i18nKey":"templates.new","bundleName":"I18nResources","translation":"New template","translationDE":"Neue Vorlage","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"text","bundleName":"I18nResources","translation":"Text","translationDE":"Text","usedInClasses":["org.projectforge.business.fibu.datev.BuchungssatzExcelImporter","org.projectforge.business.fibu.datev.DatevImportDao","org.projectforge.business.orga.ContractDO","org.projectforge.common.MimeType","org.projectforge.export.SVGHelper","org.projectforge.rest.calendar.BarcodeServicesRest","org.projectforge.rest.orga.AccountingRecordPagesRest","org.projectforge.rest.orga.ContractPagesRest","org.projectforge.rest.pub.MessagingServiceRest","org.projectforge.rest.pub.PhoneLookupRest","org.projectforge.web.fibu.AbstractRechnungEditForm","org.projectforge.web.fibu.AccountingRecordEditForm","org.projectforge.web.fibu.AccountingRecordListPage","org.projectforge.web.wicket.components.AjaxMaxLengthEditableLabel","org.projectforge.web.wicket.components.MaxLengthTextField","org.projectforge.web.wicket.components.SingleTextFieldPanel","org.projectforge.web.wicket.flowlayout.DiffTextPanel","org.projectforge.web.wicket.flowlayout.DivTextPanel","org.projectforge.web.wicket.flowlayout.FieldsetPanel","org.projectforge.web.wicket.flowlayout.IconButtonPanel","org.projectforge.web.wicket.flowlayout.ParTextPanel","org.projectforge.web.wicket.flowlayout.TextLinkPanel","org.projectforge.web.wicket.flowlayout.TextPanel","org.projectforge.web.wicket.flowlayout.TextStyle","org.projectforge.web.wicket.flowlayout.ToggleContainerPanel"],"usedInFiles":["./projectforge-wicket/src/main/java/org/projectforge/web/common/ColorPickerPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/core/NavTopPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/fibu/CustomerSelectPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/fibu/EmployeeSelectPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/fibu/KontoSelectPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/fibu/NewCustomerSelectPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/fibu/NewProjektSelectPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/gantt/GanttChartEditTreeTablePanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/humanresources/HRPlanningEditTablePanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/user/NewGroupSelectPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/user/UserSelectPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/components/DatePanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/components/JodaDatePanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/components/LocalDatePanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/components/SingleTextFieldPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/components/TextFieldOrLabelPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/components/TimeZonePanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/flowlayout/DiffTextPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/flowlayout/DivTextPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/flowlayout/IconButtonPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/flowlayout/InputPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/flowlayout/ParTextPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/flowlayout/Select2MultiChoicePanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/flowlayout/TextLinkPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/flowlayout/TextPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/flowlayout/ToggleContainerPanel.html"]}, @@ -2335,43 +2389,43 @@ {"i18nKey":"timeleft.years.one","bundleName":"I18nResources","translation":"in a year","translationDE":"in einem Jahr","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"timesheet","bundleName":"I18nResources","translation":"Time-sheet","translationDE":"Zeitbericht","usedInClasses":["org.projectforge.Constants","org.projectforge.business.timesheet.TimesheetFavoritesService","org.projectforge.framework.persistence.DaoConst","org.projectforge.model.rest.RestPaths","org.projectforge.registry.Registry","org.projectforge.rest.calendar.CalendarServicesRest","org.projectforge.rest.calendar.FullCalendarEvent","org.projectforge.rest.calendar.TimesheetEventsProvider","org.projectforge.web.calendar.TimesheetEventsProvider","org.projectforge.web.timesheet.TimesheetEditPage","org.projectforge.web.timesheet.TimesheetListPage"],"usedInFiles":[]}, {"i18nKey":"timesheet.break","bundleName":"I18nResources","translation":"Break","translationDE":"leer","usedInClasses":["org.projectforge.rest.calendar.TimesheetEventsProvider","org.projectforge.web.calendar.TimesheetEventsProvider"],"usedInFiles":[]}, - {"i18nKey":"timesheet.description","bundleName":"I18nResources","translation":"Activity report","translationDE":"Tätigkeitsbericht","usedInClasses":["org.projectforge.business.humanresources.HRPlanningExport","org.projectforge.business.timesheet.TimesheetDO","org.projectforge.business.timesheet.TimesheetExport","org.projectforge.rest.TimesheetPagesRest","org.projectforge.web.timesheet.TimesheetEditForm","org.projectforge.web.timesheet.TimesheetEditSelectRecentDialogPanel"],"usedInFiles":[]}, + {"i18nKey":"timesheet.description","bundleName":"I18nResources","translation":"Activity report","translationDE":"Tätigkeitsbericht","usedInClasses":["org.projectforge.business.humanresources.HRPlanningExport","org.projectforge.business.timesheet.TimesheetDO","org.projectforge.business.timesheet.TimesheetExport","org.projectforge.rest.TimesheetPagesRest","org.projectforge.web.timesheet.TimesheetEditForm","org.projectforge.web.timesheet.TimesheetEditSelectRecentDialogPanel"],"usedInFiles":[]}, {"i18nKey":"timesheet.duration","bundleName":"I18nResources","translation":"Duration","translationDE":"Dauer","usedInClasses":["org.projectforge.business.timesheet.TimesheetExport","org.projectforge.renderer.custom.MicromataFormatter","org.projectforge.rest.TimesheetPagesRest","org.projectforge.web.calendar.CalendarForm","org.projectforge.web.teamcal.event.MyWicketEvent","org.projectforge.web.timesheet.TimesheetListPage"],"usedInFiles":[]}, - {"i18nKey":"timesheet.error.copyNoMatchingKost2","bundleName":"I18nResources","translation":"No Kost2 available or matching for this timesheet copy. This probably means the structure elemnt of this timesheet has moved into another subtree. resolve problem manually by choosing kost2.","translationDE":"Keine Kost2 Id für diese Zeiberichtskopie verfügbar oder keine übereinstimmende mit dem Orginal gefunden. Wahrscheinlich wurde das Strukturelement des Zeitberichts in einen anderen Unterbaum verschoben. Zur Lösung des Problems bitte manuell Kost2 Id wählen.","usedInClasses":["org.projectforge.web.calendar.CalendarPanel"],"usedInFiles":[]}, + {"i18nKey":"timesheet.error.copyNoMatchingKost2","bundleName":"I18nResources","translation":"No Kost2 available or matching for this timesheet copy. This probably means the structure elemnt of this timesheet has moved into another subtree. resolve problem manually by choosing kost2.","translationDE":"Keine Kost2 Id für diese Zeiberichtskopie verfügbar oder keine übereinstimmende mit dem Orginal gefunden. Wahrscheinlich wurde das Strukturelement des Zeitberichts in einen anderen Unterbaum verschoben. Zur Lösung des Problems bitte manuell Kost2 Id wählen.","usedInClasses":["org.projectforge.web.calendar.CalendarPanel"],"usedInFiles":[]}, {"i18nKey":"timesheet.error.filter.needMore","bundleName":"I18nResources","translation":"Please enter further filter settings such as date or structure element.","translationDE":"Zu wenige Filterangaben: Bitte Datum und/oder Strukturelement angeben.","usedInClasses":["org.projectforge.web.timesheet.TimesheetListForm"],"usedInFiles":[]}, - {"i18nKey":"timesheet.error.invalidKost2","bundleName":"I18nResources","translation":"No valid cost 2 id.","translationDE":"Keine gültige Kost2 Id.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao"],"usedInFiles":[]}, + {"i18nKey":"timesheet.error.invalidKost2","bundleName":"I18nResources","translation":"No valid cost 2 id.","translationDE":"Keine gültige Kost2 Id.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao"],"usedInFiles":[]}, {"i18nKey":"timesheet.error.invalidTaskId","bundleName":"I18nResources","translation":"Structure element not found.","translationDE":"Das Strukturelement wurde leider nicht gefunden.","usedInClasses":["org.projectforge.web.task.TaskSelectAutoCompleteFormComponent"],"usedInFiles":[]}, - {"i18nKey":"timesheet.error.kost2NeededChooseSubTask","bundleName":"I18nResources","translation":"Kost2 required, please choose sub structure element with Kost2s.","translationDE":"Bitte Kost2 auswählen und ggf. Strukturunterelement mit Kost2s bebuchen.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao","org.projectforge.web.timesheet.TimesheetEditForm"],"usedInFiles":[]}, - {"i18nKey":"timesheet.error.kost2Required","bundleName":"I18nResources","translation":"Please choose cost 2 id.","translationDE":"Bitte Kost2 auswählen.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao","org.projectforge.web.fibu.Kost2DropDownChoice","org.projectforge.web.timesheet.TimesheetEditForm"],"usedInFiles":[]}, - {"i18nKey":"timesheet.error.massupdate.couldnotconvertkost2","bundleName":"I18nResources","translation":"Mass update not possible for time sheet. The destination task has multiple kost2 entries and no corresponding kost2 art.","translationDE":"Massenänderung für Zeitbericht nicht möglich. Das Ziel-Strukturelement hat mehrere Kost2-Einträge aber es gibt keine passende Kost2-Art.","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"timesheet.error.massupdate.kost2notsupported","bundleName":"I18nResources","translation":"Mass update is not possible for time sheet. The destination task does not support the given kost2.","translationDE":"Massenänderung für Zeitbericht nicht möglich. Das Ziel-Strukturelement unterstützt nicht die gewählte Kost2.","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"timesheet.error.massupdate.kost2null","bundleName":"I18nResources","translation":"Mass update not possible for time sheet. The destination task requires a kost2.","translationDE":"Massenänderung für Zeitbericht nicht möglich. Für das Ziel-Strukturelement wird eine Kost2 benötigt.","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"timesheet.error.maximumDurationExceeded","bundleName":"I18nResources","translation":"Maximum of duration exceeded.","translationDE":"Maximal zugelassene Dauer überschritten.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao","org.projectforge.web.timesheet.TimesheetEditForm"],"usedInFiles":[]}, - {"i18nKey":"timesheet.error.noAccess","bundleName":"I18nResources","translation":"Not possible, missing rights for this action.","translationDE":"Fehlende Berechtigung für die ausgewählte Aktion.","usedInClasses":["org.projectforge.web.calendar.CalendarPanel"],"usedInFiles":[]}, - {"i18nKey":"timesheet.error.overlapping","bundleName":"I18nResources","translation":"Not possible, timesheet is overlapping.","translationDE":"Die ausgewählte Aktion ist aufgrund einer zeitlichen Überschneidung nicht möglich.","usedInClasses":["org.projectforge.web.calendar.CalendarPanel"],"usedInFiles":[]}, - {"i18nKey":"timesheet.error.taskNotBookable.onlyLeafsAllowedForBooking","bundleName":"I18nResources","translation":"The structure element {0} is not bookable because only leaf elements are bookable.","translationDE":"Das Strukturelement {0} kann nicht bebucht werden, da nur Strukturblätterelemente (d. h. Strukturelemente ohne Unterelementen) bebucht werden können.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao"],"usedInFiles":[]}, + {"i18nKey":"timesheet.error.kost2NeededChooseSubTask","bundleName":"I18nResources","translation":"Kost2 required, please choose sub structure element with Kost2s.","translationDE":"Bitte Kost2 auswählen und ggf. Strukturunterelement mit Kost2s bebuchen.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao","org.projectforge.web.timesheet.TimesheetEditForm"],"usedInFiles":[]}, + {"i18nKey":"timesheet.error.kost2Required","bundleName":"I18nResources","translation":"Please choose cost 2 id.","translationDE":"Bitte Kost2 auswählen.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao","org.projectforge.web.fibu.Kost2DropDownChoice","org.projectforge.web.timesheet.TimesheetEditForm"],"usedInFiles":[]}, + {"i18nKey":"timesheet.error.massupdate.couldnotconvertkost2","bundleName":"I18nResources","translation":"Mass update not possible for time sheet. The destination task has multiple kost2 entries and no corresponding kost2 art.","translationDE":"Massenänderung für Zeitbericht nicht möglich. Das Ziel-Strukturelement hat mehrere Kost2-Einträge aber es gibt keine passende Kost2-Art.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"timesheet.error.massupdate.kost2notsupported","bundleName":"I18nResources","translation":"Mass update is not possible for time sheet. The destination task does not support the given kost2.","translationDE":"Massenänderung für Zeitbericht nicht möglich. Das Ziel-Strukturelement unterstützt nicht die gewählte Kost2.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"timesheet.error.massupdate.kost2null","bundleName":"I18nResources","translation":"Mass update not possible for time sheet. The destination task requires a kost2.","translationDE":"Massenänderung für Zeitbericht nicht möglich. Für das Ziel-Strukturelement wird eine Kost2 benötigt.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"timesheet.error.maximumDurationExceeded","bundleName":"I18nResources","translation":"Maximum of duration exceeded.","translationDE":"Maximal zugelassene Dauer überschritten.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao","org.projectforge.web.timesheet.TimesheetEditForm"],"usedInFiles":[]}, + {"i18nKey":"timesheet.error.noAccess","bundleName":"I18nResources","translation":"Not possible, missing rights for this action.","translationDE":"Fehlende Berechtigung für die ausgewählte Aktion.","usedInClasses":["org.projectforge.web.calendar.CalendarPanel"],"usedInFiles":[]}, + {"i18nKey":"timesheet.error.overlapping","bundleName":"I18nResources","translation":"Not possible, timesheet is overlapping.","translationDE":"Die ausgewählte Aktion ist aufgrund einer zeitlichen Überschneidung nicht möglich.","usedInClasses":["org.projectforge.web.calendar.CalendarPanel"],"usedInFiles":[]}, + {"i18nKey":"timesheet.error.taskNotBookable.onlyLeafsAllowedForBooking","bundleName":"I18nResources","translation":"The structure element {0} is not bookable because only leaf elements are bookable.","translationDE":"Das Strukturelement {0} kann nicht bebucht werden, da nur Strukturblätterelemente (d. h. Strukturelemente ohne Unterelementen) bebucht werden können.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao"],"usedInFiles":[]}, {"i18nKey":"timesheet.error.taskNotBookable.orderPositionsFoundInSubTasks","bundleName":"I18nResources","translation":"The structure element {0} is not bookable because at least one order position is assigned to a sub element.","translationDE":"Das Strukturelement {0} kann nicht bebucht werden, da Auftragszuordnungen in Unterelementen existieren.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao"],"usedInFiles":[]}, - {"i18nKey":"timesheet.error.taskNotBookable.taskClosedForBooking","bundleName":"I18nResources","translation":"The structure element {0} is not bookable because the element is closed for booking of time sheets.","translationDE":"Das Strukturelement {0} kann nicht bebucht werden, da es für Zeitberichtsbuchungen geschlossen ist.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao"],"usedInFiles":[]}, - {"i18nKey":"timesheet.error.taskNotBookable.taskDeleted","bundleName":"I18nResources","translation":"The structure element {0} is not bookable because it''s deleted.","translationDE":"Das Strukturelement {0} kann nicht bebucht werden, da es gelöscht ist.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao"],"usedInFiles":[]}, - {"i18nKey":"timesheet.error.taskNotBookable.taskNotOpened","bundleName":"I18nResources","translation":"The structure element {0} is not bookable because it''s not opened.","translationDE":"Das Strukturelement {0} kann nicht bebucht werden, da es nicht geöffnet ist.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao","org.projectforge.web.calendar.CalendarPanel"],"usedInFiles":[]}, - {"i18nKey":"timesheet.error.taskNotBookable.treeClosedForBooking","bundleName":"I18nResources","translation":"The structure element {0} is not bookable because the structure tree is closed for booking of time sheets.","translationDE":"Das Strukturelement {0} kann nicht bebucht werden, da der komplette Strukturbaum für Zeitberichtsbuchungen geschlossen ist.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao"],"usedInFiles":[]}, + {"i18nKey":"timesheet.error.taskNotBookable.taskClosedForBooking","bundleName":"I18nResources","translation":"The structure element {0} is not bookable because the element is closed for booking of time sheets.","translationDE":"Das Strukturelement {0} kann nicht bebucht werden, da es für Zeitberichtsbuchungen geschlossen ist.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao"],"usedInFiles":[]}, + {"i18nKey":"timesheet.error.taskNotBookable.taskDeleted","bundleName":"I18nResources","translation":"The structure element {0} is not bookable because it''s deleted.","translationDE":"Das Strukturelement {0} kann nicht bebucht werden, da es gelöscht ist.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao"],"usedInFiles":[]}, + {"i18nKey":"timesheet.error.taskNotBookable.taskNotOpened","bundleName":"I18nResources","translation":"The structure element {0} is not bookable because it''s not opened.","translationDE":"Das Strukturelement {0} kann nicht bebucht werden, da es nicht geöffnet ist.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao","org.projectforge.web.calendar.CalendarPanel"],"usedInFiles":[]}, + {"i18nKey":"timesheet.error.taskNotBookable.treeClosedForBooking","bundleName":"I18nResources","translation":"The structure element {0} is not bookable because the structure tree is closed for booking of time sheets.","translationDE":"Das Strukturelement {0} kann nicht bebucht werden, da der komplette Strukturbaum für Zeitberichtsbuchungen geschlossen ist.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao"],"usedInFiles":[]}, {"i18nKey":"timesheet.error.timeperiodOverlapDetection","bundleName":"I18nResources","translation":"The time sheet has a time period collision with time sheet #{0} from {1} to {2} for the same user.","translationDE":"Der Zeitbericht kollidiert mit dem Zeitbericht #{0} (gleiche:r Benutzer:in) von {1} bis {2}.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao"],"usedInFiles":[]}, - {"i18nKey":"timesheet.error.timesheetProtectionVioloation","bundleName":"I18nResources","translation":"The time sheet violates the time sheet protection of structure element ''{0}'' which is set until {1}. Please contact the accounting staff.","translationDE":"Der Zeitbericht verletzt den Zeitberichtsschutz des Strukturelements ''{0}'', welcher bis einschließlich {1} gesetzt ist. Bitte Rücksprache mit der Buchhaltung nehmen.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao"],"usedInFiles":[]}, + {"i18nKey":"timesheet.error.timesheetProtectionVioloation","bundleName":"I18nResources","translation":"The time sheet violates the time sheet protection of structure element ''{0}'' which is set until {1}. Please contact the accounting staff.","translationDE":"Der Zeitbericht verletzt den Zeitberichtsschutz des Strukturelements ''{0}'', welcher bis einschließlich {1} gesetzt ist. Bitte Rücksprache mit der Buchhaltung nehmen.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao"],"usedInFiles":[]}, {"i18nKey":"timesheet.error.zeroDuration","bundleName":"I18nResources","translation":"Time sheet has zero duration.","translationDE":"Der Zeitbericht hat keine Dauer.","usedInClasses":["org.projectforge.business.timesheet.TimesheetDao","org.projectforge.web.timesheet.TimesheetEditForm"],"usedInFiles":[]}, {"i18nKey":"timesheet.filter.withTimeperiodCollision","bundleName":"I18nResources","translation":"Only collisions","translationDE":"Nur kollidierte","usedInClasses":["org.projectforge.web.timesheet.TimesheetListForm"],"usedInFiles":[]}, - {"i18nKey":"timesheet.filter.withTimeperiodCollision.tooltip","bundleName":"I18nResources","translation":"Show only time-sheets with collisions, meaning time-sheets with an overlap of the time-period with another time-sheet of the same user.","translationDE":"Es werden nur Zeitberichte angezeigt, die mit anderen Zeitberichten (gleiche:r Nutzer:in) kollidieren (sich überlappen).","usedInClasses":["org.projectforge.web.timesheet.TimesheetListForm"],"usedInFiles":[]}, - {"i18nKey":"timesheet.iCalSubscription","bundleName":"I18nResources","translation":"Subscribe time sheets in iCal format (*.ics) for the logged-in user.","translationDE":"Zeitberichte im iCal-Format für den/die angemeldete:n Benutzer:in exportieren (*.ics).","usedInClasses":["org.projectforge.web.humanresources.HRPlanningListPage","org.projectforge.web.timesheet.TimesheetListPage"],"usedInFiles":[]}, + {"i18nKey":"timesheet.filter.withTimeperiodCollision.tooltip","bundleName":"I18nResources","translation":"Show only time-sheets with collisions, meaning time-sheets with an overlap of the time-period with another time-sheet of the same user.","translationDE":"Es werden nur Zeitberichte angezeigt, die mit anderen Zeitberichten (gleiche:r Nutzer:in) kollidieren (sich überlappen).","usedInClasses":["org.projectforge.web.timesheet.TimesheetListForm"],"usedInFiles":[]}, + {"i18nKey":"timesheet.iCalSubscription","bundleName":"I18nResources","translation":"Subscribe time sheets in iCal format (*.ics) for the logged-in user.","translationDE":"Zeitberichte im iCal-Format für den/die angemeldete:n Benutzer:in exportieren (*.ics).","usedInClasses":["org.projectforge.web.humanresources.HRPlanningListPage","org.projectforge.web.timesheet.TimesheetListPage"],"usedInFiles":[]}, {"i18nKey":"timesheet.icsExport","bundleName":"I18nResources","translation":"ics export","translationDE":"ics-Export","usedInClasses":["org.projectforge.web.humanresources.HRPlanningListPage","org.projectforge.web.timesheet.TimesheetListPage"],"usedInFiles":[]}, {"i18nKey":"timesheet.location","bundleName":"I18nResources","translation":"Location","translationDE":"Ort","usedInClasses":["org.projectforge.business.timesheet.TimesheetDO","org.projectforge.business.timesheet.TimesheetExport","org.projectforge.renderer.custom.MicromataFormatter","org.projectforge.rest.TimesheetPagesRest","org.projectforge.rest.calendar.TimesheetEventsProvider","org.projectforge.web.calendar.TimesheetEventsProvider","org.projectforge.web.teamcal.event.TeamCalEventProvider","org.projectforge.web.timesheet.TimesheetEditSelectRecentDialogPanel","org.projectforge.web.timesheet.TimesheetListPage","org.projectforge.web.timesheet.TimesheetPageSupport"],"usedInFiles":[]}, - {"i18nKey":"timesheet.location.tooltip","bundleName":"I18nResources","translation":"You may remove locations from the auto-completion list by simply clicking the remove icon (cross) at the end of the line of the location to delete. Any removed location can be used again by simply using it again.","translationDE":"Einträge aus der Vorschlagsliste können entfernt werden, indem einfach das Entfernen-Symbol (Kreuz) am Ende der Zeile des zu löschenden Orts geklickt wird. Entfernte Orte können wieder aufgenommen werden, indem sie einfach wieder erneut verwendet werden.","usedInClasses":["org.projectforge.web.timesheet.TimesheetPageSupport"],"usedInFiles":[]}, - {"i18nKey":"timesheet.massupdate.kost.info","bundleName":"I18nResources","translation":"If you update the structure element of a project to another resulting in other available cost entries, cost2 entries will be migrated while preserving the type of cost if possible (last 2 digits).","translationDE":"Wenn Zeitberichte eines Projekts zu einem anderen transferiert werden, wird der Kostenträger automatisch migriert, falls möglich, d. h. die Kostenart (letzte beiden Ziffern) bleibt erhalten.","usedInClasses":["org.projectforge.rest.TimesheetMultiSelectedPageRest"],"usedInFiles":[]}, - {"i18nKey":"timesheet.massupdate.updateTask","bundleName":"I18nResources","translation":"Update structure element for all time sheets.","translationDE":"Strukturelement für alle Zeitberichte ändern","usedInClasses":["org.projectforge.rest.TimesheetMultiSelectedPageRest"],"usedInFiles":[]}, + {"i18nKey":"timesheet.location.tooltip","bundleName":"I18nResources","translation":"You may remove locations from the auto-completion list by simply clicking the remove icon (cross) at the end of the line of the location to delete. Any removed location can be used again by simply using it again.","translationDE":"Einträge aus der Vorschlagsliste können entfernt werden, indem einfach das Entfernen-Symbol (Kreuz) am Ende der Zeile des zu löschenden Orts geklickt wird. Entfernte Orte können wieder aufgenommen werden, indem sie einfach wieder erneut verwendet werden.","usedInClasses":["org.projectforge.web.timesheet.TimesheetPageSupport"],"usedInFiles":[]}, + {"i18nKey":"timesheet.massupdate.kost.info","bundleName":"I18nResources","translation":"If you update the structure element of a project to another resulting in other available cost entries, cost2 entries will be migrated while preserving the type of cost if possible (last 2 digits).","translationDE":"Wenn Zeitberichte eines Projekts zu einem anderen transferiert werden, wird der Kostenträger automatisch migriert, falls möglich, d. h. die Kostenart (letzte beiden Ziffern) bleibt erhalten.","usedInClasses":["org.projectforge.rest.TimesheetMultiSelectedPageRest"],"usedInFiles":[]}, + {"i18nKey":"timesheet.massupdate.updateTask","bundleName":"I18nResources","translation":"Update structure element for all time sheets.","translationDE":"Strukturelement für alle Zeitberichte ändern","usedInClasses":["org.projectforge.rest.TimesheetMultiSelectedPageRest"],"usedInFiles":[]}, {"i18nKey":"timesheet.multiselected.title","bundleName":"I18nResources","translation":"Multi selected time sheets","translationDE":"Mehrfachauswahl Zeitberichte","usedInClasses":["org.projectforge.rest.TimesheetMultiSelectedPageRest"],"usedInFiles":[]}, {"i18nKey":"timesheet.recent","bundleName":"I18nResources","translation":"Recent","translationDE":"Zuletzt verwendet","usedInClasses":["org.projectforge.rest.TimesheetPagesRest"],"usedInFiles":[]}, {"i18nKey":"timesheet.recent.select","bundleName":"I18nResources","translation":"Recent time sheets","translationDE":"Zuletzt bearbeitete Berichte zur Auswahl","usedInClasses":["org.projectforge.web.timesheet.TimesheetEditForm"],"usedInFiles":[]}, {"i18nKey":"timesheet.recenttasks.select","bundleName":"I18nResources","translation":"--- Recent structure elements ---","translationDE":"--- Zuletzt verwendete Strukturelemente ---","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"timesheet.reference","bundleName":"I18nResources","translation":"Reference","translationDE":"Referenz","usedInClasses":["org.projectforge.business.timesheet.TimesheetDO","org.projectforge.business.timesheet.TimesheetExport","org.projectforge.rest.TimesheetPagesRest","org.projectforge.web.timesheet.TimesheetEditForm","org.projectforge.web.timesheet.TimesheetListPage","org.projectforge.web.timesheet.TimesheetPageSupport"],"usedInFiles":[]}, - {"i18nKey":"timesheet.reference.info","bundleName":"I18nResources","translation":"References may be used for grouping time sheets. Already used references for the same or any descendant structure element in time sheets also by other users can be easily used via auto-completion.","translationDE":"Referenzen können zur Gruppierung von Zeitberichten genutzt werden. Bereits benutzte Referenzen in Zeitberichten in der Hierarchie des Strukturelements (auch von anderen Usern) können per Autovervollständigung genutzt werden.","usedInClasses":["org.projectforge.rest.TimesheetPagesRest","org.projectforge.web.timesheet.TimesheetEditForm"],"usedInFiles":[]}, + {"i18nKey":"timesheet.reference.info","bundleName":"I18nResources","translation":"References may be used for grouping time sheets. Already used references for the same or any descendant structure element in time sheets also by other users can be easily used via auto-completion.","translationDE":"Referenzen können zur Gruppierung von Zeitberichten genutzt werden. Bereits benutzte Referenzen in Zeitberichten in der Hierarchie des Strukturelements (auch von anderen Usern) können per Autovervollständigung genutzt werden.","usedInClasses":["org.projectforge.rest.TimesheetPagesRest","org.projectforge.web.timesheet.TimesheetEditForm"],"usedInFiles":[]}, {"i18nKey":"timesheet.signatureEmployee","bundleName":"I18nResources","translation":"Signature employee","translationDE":"Unterschrift Mitarbeiter:in","usedInClasses":["org.projectforge.web.fibu.MonthlyEmployeeReportPage"],"usedInFiles":[]}, {"i18nKey":"timesheet.signatureProjectLeader","bundleName":"I18nResources","translation":"Signature project leader","translationDE":"Unterschrift Projektmanagement","usedInClasses":["org.projectforge.web.fibu.MonthlyEmployeeReportPage"],"usedInFiles":[]}, {"i18nKey":"timesheet.startTime","bundleName":"I18nResources","translation":"Start time","translationDE":"Beginn","usedInClasses":["org.projectforge.business.timesheet.TimesheetDO","org.projectforge.business.timesheet.TimesheetExport","org.projectforge.web.humanresources.HRPlanningEditForm"],"usedInFiles":[]}, @@ -2380,10 +2434,10 @@ {"i18nKey":"timesheet.taskReference","bundleName":"I18nResources","translation":"Reference of element","translationDE":"Elementreferenz","usedInClasses":["org.projectforge.business.timesheet.TimesheetExport"],"usedInFiles":[]}, {"i18nKey":"timesheet.templates","bundleName":"I18nResources","translation":"Templates","translationDE":"Vorlagen","usedInClasses":["org.projectforge.rest.TimesheetPagesRest","org.projectforge.web.timesheet.TimesheetEditForm","org.projectforge.web.timesheet.TimesheetListPage"],"usedInFiles":[]}, {"i18nKey":"timesheet.templates.migrationOfLegacy.button","bundleName":"I18nResources","translation":"Old templates ","translationDE":"Alte Vorlagen","usedInClasses":["org.projectforge.rest.TimesheetPagesRest"],"usedInFiles":[]}, - {"i18nKey":"timesheet.templates.migrationOfLegacy.confirmationMessage","bundleName":"I18nResources","translation":"Do you want to import your old templates from the classical version now? Any existing entry will not be overwritten.","translationDE":"Sollen alte Vorlagen jetzt übernommen werden? Vorhandene Vorlagen werden nicht überschrieben.","usedInClasses":["org.projectforge.rest.TimesheetPagesRest"],"usedInFiles":[]}, - {"i18nKey":"timesheet.templates.migrationOfLegacy.tooltip","bundleName":"I18nResources","translation":"Imports old templates of classical version.","translationDE":"Übernehmen alter Vorlagen der klassischen Version.","usedInClasses":["org.projectforge.rest.TimesheetPagesRest"],"usedInFiles":[]}, + {"i18nKey":"timesheet.templates.migrationOfLegacy.confirmationMessage","bundleName":"I18nResources","translation":"Do you want to import your old templates from the classical version now? Any existing entry will not be overwritten.","translationDE":"Sollen alte Vorlagen jetzt übernommen werden? Vorhandene Vorlagen werden nicht überschrieben.","usedInClasses":["org.projectforge.rest.TimesheetPagesRest"],"usedInFiles":[]}, + {"i18nKey":"timesheet.templates.migrationOfLegacy.tooltip","bundleName":"I18nResources","translation":"Imports old templates of classical version.","translationDE":"Übernehmen alter Vorlagen der klassischen Version.","usedInClasses":["org.projectforge.rest.TimesheetPagesRest"],"usedInFiles":[]}, {"i18nKey":"timesheet.templates.new","bundleName":"I18nResources","translation":"New template","translationDE":"Neue Vorlage","usedInClasses":["org.projectforge.rest.TimesheetPagesRest"],"usedInFiles":[]}, - {"i18nKey":"timesheet.templates.new.tooltip","bundleName":"I18nResources","translation":"You may create a new template by filling out this form and save it here by name.","translationDE":"Du kannst dieses Formular ausfüllen und anschließend als Vorlage definieren, indem du hier einen Namen vergibst.","usedInClasses":["org.projectforge.rest.TimesheetPagesRest"],"usedInFiles":[]}, + {"i18nKey":"timesheet.templates.new.tooltip","bundleName":"I18nResources","translation":"You may create a new template by filling out this form and save it here by name.","translationDE":"Du kannst dieses Formular ausfüllen und anschließend als Vorlage definieren, indem du hier einen Namen vergibst.","usedInClasses":["org.projectforge.rest.TimesheetPagesRest"],"usedInFiles":[]}, {"i18nKey":"timesheet.timesheets","bundleName":"I18nResources","translation":"Time sheets","translationDE":"Zeitberichte","usedInClasses":["org.projectforge.business.timesheet.TimesheetExport","org.projectforge.rest.calendar.CalendarSubscriptionInfoPageRest","org.projectforge.rest.pub.CalendarSubscriptionServiceRest","org.projectforge.web.teamcal.dialog.TeamCalFilterDialog"],"usedInFiles":[]}, {"i18nKey":"timesheet.title.add","bundleName":"I18nResources","translation":"New time sheet","translationDE":"Neuer Zeitbericht","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"timesheet.title.edit","bundleName":"I18nResources","translation":"Edit time sheet","translationDE":"Zeitbericht bearbeiten","usedInClasses":["org.projectforge.web.timesheet.TimesheetEditPage","org.projectforge.web.timesheet.TimesheetListPage"],"usedInFiles":[]}, @@ -2394,45 +2448,45 @@ {"i18nKey":"timestamp","bundleName":"I18nResources","translation":"Time stamp","translationDE":"Zeitstempel","usedInClasses":["org.projectforge.common.logging.LoggingEventData","org.projectforge.framework.persistence.database.HistoryMigrateService","org.projectforge.plugins.datatransfer.DataTransferAuditDao","org.projectforge.plugins.datatransfer.rest.DataTransferAuditPageRest","org.projectforge.rest.admin.LogViewerEvent","org.projectforge.rest.admin.LogViewerPageRest","org.projectforge.web.wicket.AbstractEditPage"],"usedInFiles":["./projectforge-business/src/main/resources/mail/mailHistoryTable.html"]}, {"i18nKey":"timezone","bundleName":"I18nResources","translation":"Time zone","translationDE":"Zeitzone","usedInClasses":["org.projectforge.framework.configuration.ConfigurationParam","org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, {"i18nKey":"tip","bundleName":"I18nResources","translation":"Tip","translationDE":"Tipp","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"title","bundleName":"I18nResources","translation":"Title","translationDE":"Titel","usedInClasses":["org.projectforge.business.address.AddressExport","org.projectforge.business.address.AddressbookDao","org.projectforge.business.gantt.GanttTaskImpl","org.projectforge.business.orga.ContractDO","org.projectforge.business.orga.ContractDao","org.projectforge.business.task.TaskDO","org.projectforge.business.task.TaskDao","org.projectforge.business.task.TaskNode","org.projectforge.business.teamcal.admin.TeamCalDao","org.projectforge.framework.ToStringUtil","org.projectforge.framework.jobs.AbstractJob","org.projectforge.mail.SendMail","org.projectforge.plugins.marketing.AddressCampaignDO","org.projectforge.plugins.marketing.AddressCampaignEditForm","org.projectforge.plugins.marketing.AddressCampaignListPage","org.projectforge.plugins.marketing.rest.AddressCampaignPagesRest","org.projectforge.renderer.custom.MicromataFormatter","org.projectforge.rest.AddressBookPagesRest","org.projectforge.rest.AddressPagesRest","org.projectforge.rest.BookPagesRest","org.projectforge.rest.TeamCalPagesRest","org.projectforge.rest.calendar.CalEventPagesRest","org.projectforge.rest.calendar.TeamEventPagesRest","org.projectforge.rest.fibu.AuftragPagesRest","org.projectforge.rest.orga.ContractPagesRest","org.projectforge.rest.task.TaskPagesRest","org.projectforge.rest.task.TaskServicesRest","org.projectforge.web.address.AddressPageSupport","org.projectforge.web.admin.AdminPage","org.projectforge.web.calendar.MyFullCalendarConfig","org.projectforge.web.fibu.AuftragsPositionFormComponent","org.projectforge.web.fibu.MonthlyEmployeeReportPage","org.projectforge.web.fibu.ReportObjectivesPanel","org.projectforge.web.gantt.GanttChartEditForm","org.projectforge.web.gantt.GanttChartEditTreeTablePanel","org.projectforge.web.gantt.GanttTreeTableNode","org.projectforge.web.task.TaskEditForm","org.projectforge.web.task.TaskListPage","org.projectforge.web.task.TaskSelectAutoCompleteFormComponent","org.projectforge.web.teamcal.admin.TeamCalEditForm","org.projectforge.web.teamcal.admin.TeamCalListPage","org.projectforge.web.teamcal.event.TeamEventEditForm","org.projectforge.web.wicket.I18nParamMap","org.projectforge.web.wicket.WicketUtils","org.projectforge.web.wicket.flowlayout.ButtonPanel"],"usedInFiles":["./projectforge-business/src/main/resources/mail/mailOpening.html","./projectforge-business/src/main/resources/mail/orderChangeNotification.html","./projectforge-business/src/main/resources/mail/todoChangeNotification.html","./projectforge-business/src/main/resources/mail/vacationMail.html","./projectforge-wicket/src/main/java/org/projectforge/web/fibu/ReportObjectivesPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/gantt/GanttChartEditTreeTablePanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/flowlayout/ButtonPanel.html"]}, - {"i18nKey":"tooltip.accesskey.addEntry","bundleName":"I18nResources","translation":"dependent of the used browser: press CTRL-N, ALT-N, ALT-Shift-N etc.","translationDE":"Je nach Browser: STRG-N, ALT-N, ALT-Shift-N etc. drücken","usedInClasses":["org.projectforge.web.wicket.WebConstants"],"usedInFiles":[]}, - {"i18nKey":"tooltip.accesskey.addEntry.title","bundleName":"I18nResources","translation":"Access key N","translationDE":"Tastaturkürzel N","usedInClasses":["org.projectforge.web.wicket.WebConstants"],"usedInFiles":[]}, - {"i18nKey":"tooltip.assign","bundleName":"I18nResources","translation":"Assign selected elements","translationDE":"Selektierte Einträge zuweisen","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"tooltip.autocomplete.language","bundleName":"I18nResources","translation":"Double click shows you all used languages in the data base (by all users) for selection.","translationDE":"Über einen Doppelklick auf das Feld werden auch alle von allen Benutzer:innen bisher verwendeten Sprachen angezeigt.","usedInClasses":["org.projectforge.web.address.AddressPageSupport"],"usedInFiles":[]}, - {"i18nKey":"tooltip.autocomplete.recentSearchTerms","bundleName":"I18nResources","translation":"Double click on the text field will display the recent search terms for selection.","translationDE":"Über einen Doppelklick auf das Feld werden die zuletzt verwendeten Suchausdrücke zur Auswahl angezeigt.","usedInClasses":["org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]}, - {"i18nKey":"tooltip.autocomplete.timeZone","bundleName":"I18nResources","translation":"Double click shows you all used time zones in the data base (by all users) for selection.","translationDE":"Über einen Doppelklick auf das Feld werden auch alle von allen Benutzer:innen bisher verwendeten Zeitzonen angezeigt.","usedInClasses":["org.projectforge.web.user.UserEditForm","org.projectforge.web.wicket.components.TimeZoneField","org.projectforge.web.wicket.components.TimeZonePanel"],"usedInFiles":[]}, - {"i18nKey":"tooltip.autocomplete.withDblClickFunction","bundleName":"I18nResources","translation":"Double click on the text field will display the favorite entries for selection.","translationDE":"Über einen Doppelklick auf das Feld werden die Favoriten zur Auswahl angezeigt.","usedInClasses":["org.projectforge.web.timesheet.TimesheetPageSupport"],"usedInFiles":[]}, - {"i18nKey":"tooltip.autocompletion.title","bundleName":"I18nResources","translation":"Auto-completion","translationDE":"Autovervollständigung","usedInClasses":["org.projectforge.web.address.AddressPageSupport","org.projectforge.web.address.PhoneCallForm"],"usedInFiles":[]}, + {"i18nKey":"title","bundleName":"I18nResources","translation":"Title","translationDE":"Titel","usedInClasses":["org.projectforge.business.address.AddressExport","org.projectforge.business.address.AddressbookDao","org.projectforge.business.gantt.GanttTaskImpl","org.projectforge.business.orga.ContractDO","org.projectforge.business.orga.ContractDao","org.projectforge.business.poll.PollDO","org.projectforge.business.task.TaskDO","org.projectforge.business.task.TaskDao","org.projectforge.business.task.TaskNode","org.projectforge.business.teamcal.admin.TeamCalDao","org.projectforge.framework.ToStringUtil","org.projectforge.framework.jobs.AbstractJob","org.projectforge.mail.SendMail","org.projectforge.plugins.marketing.AddressCampaignDO","org.projectforge.plugins.marketing.AddressCampaignEditForm","org.projectforge.plugins.marketing.AddressCampaignListPage","org.projectforge.plugins.marketing.rest.AddressCampaignPagesRest","org.projectforge.renderer.custom.MicromataFormatter","org.projectforge.rest.AddressBookPagesRest","org.projectforge.rest.AddressPagesRest","org.projectforge.rest.BookPagesRest","org.projectforge.rest.TeamCalPagesRest","org.projectforge.rest.calendar.CalEventPagesRest","org.projectforge.rest.calendar.TeamEventPagesRest","org.projectforge.rest.fibu.AuftragPagesRest","org.projectforge.rest.orga.ContractPagesRest","org.projectforge.rest.poll.PollInfoPageRest","org.projectforge.rest.poll.PollPageRest","org.projectforge.rest.task.TaskPagesRest","org.projectforge.rest.task.TaskServicesRest","org.projectforge.web.address.AddressPageSupport","org.projectforge.web.admin.AdminPage","org.projectforge.web.calendar.MyFullCalendarConfig","org.projectforge.web.fibu.AuftragsPositionFormComponent","org.projectforge.web.fibu.MonthlyEmployeeReportPage","org.projectforge.web.fibu.ReportObjectivesPanel","org.projectforge.web.gantt.GanttChartEditForm","org.projectforge.web.gantt.GanttChartEditTreeTablePanel","org.projectforge.web.gantt.GanttTreeTableNode","org.projectforge.web.task.TaskEditForm","org.projectforge.web.task.TaskListPage","org.projectforge.web.task.TaskSelectAutoCompleteFormComponent","org.projectforge.web.teamcal.admin.TeamCalEditForm","org.projectforge.web.teamcal.admin.TeamCalListPage","org.projectforge.web.teamcal.event.TeamEventEditForm","org.projectforge.web.wicket.I18nParamMap","org.projectforge.web.wicket.WicketUtils","org.projectforge.web.wicket.flowlayout.ButtonPanel"],"usedInFiles":["./projectforge-business/src/main/resources/mail/mailOpening.html","./projectforge-business/src/main/resources/mail/orderChangeNotification.html","./projectforge-business/src/main/resources/mail/todoChangeNotification.html","./projectforge-business/src/main/resources/mail/vacationMail.html","./projectforge-wicket/src/main/java/org/projectforge/web/fibu/ReportObjectivesPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/gantt/GanttChartEditTreeTablePanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/wicket/flowlayout/ButtonPanel.html"]}, + {"i18nKey":"tooltip.accesskey.addEntry","bundleName":"I18nResources","translation":"dependent of the used browser: press CTRL-N, ALT-N, ALT-Shift-N etc.","translationDE":"Je nach Browser: STRG-N, ALT-N, ALT-Shift-N etc. drücken","usedInClasses":["org.projectforge.web.wicket.WebConstants"],"usedInFiles":[]}, + {"i18nKey":"tooltip.accesskey.addEntry.title","bundleName":"I18nResources","translation":"Access key N","translationDE":"Tastaturkürzel N","usedInClasses":["org.projectforge.web.wicket.WebConstants"],"usedInFiles":[]}, + {"i18nKey":"tooltip.assign","bundleName":"I18nResources","translation":"Assign selected elements","translationDE":"Selektierte Einträge zuweisen","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"tooltip.autocomplete.language","bundleName":"I18nResources","translation":"Double click shows you all used languages in the data base (by all users) for selection.","translationDE":"Über einen Doppelklick auf das Feld werden auch alle von allen Benutzer:innen bisher verwendeten Sprachen angezeigt.","usedInClasses":["org.projectforge.web.address.AddressPageSupport"],"usedInFiles":[]}, + {"i18nKey":"tooltip.autocomplete.recentSearchTerms","bundleName":"I18nResources","translation":"Double click on the text field will display the recent search terms for selection.","translationDE":"Über einen Doppelklick auf das Feld werden die zuletzt verwendeten Suchausdrücke zur Auswahl angezeigt.","usedInClasses":["org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]}, + {"i18nKey":"tooltip.autocomplete.timeZone","bundleName":"I18nResources","translation":"Double click shows you all used time zones in the data base (by all users) for selection.","translationDE":"Über einen Doppelklick auf das Feld werden auch alle von allen Benutzer:innen bisher verwendeten Zeitzonen angezeigt.","usedInClasses":["org.projectforge.web.user.UserEditForm","org.projectforge.web.wicket.components.TimeZoneField","org.projectforge.web.wicket.components.TimeZonePanel"],"usedInFiles":[]}, + {"i18nKey":"tooltip.autocomplete.withDblClickFunction","bundleName":"I18nResources","translation":"Double click on the text field will display the favorite entries for selection.","translationDE":"Über einen Doppelklick auf das Feld werden die Favoriten zur Auswahl angezeigt.","usedInClasses":["org.projectforge.web.timesheet.TimesheetPageSupport"],"usedInFiles":[]}, + {"i18nKey":"tooltip.autocompletion.title","bundleName":"I18nResources","translation":"Auto-completion","translationDE":"Autovervollständigung","usedInClasses":["org.projectforge.web.address.AddressPageSupport","org.projectforge.web.address.PhoneCallForm"],"usedInFiles":[]}, {"i18nKey":"tooltip.cancel","bundleName":"I18nResources","translation":"Cancel","translationDE":"Abbrechen","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"tooltip.entry.delete","bundleName":"I18nResources","translation":"Delete entry","translationDE":"Eintrag löschen","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"tooltip.entry.markAsDeleted","bundleName":"I18nResources","translation":"Mark entry as deleted","translationDE":"Eintrag als gelöscht markieren","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"tooltip.entry.delete","bundleName":"I18nResources","translation":"Delete entry","translationDE":"Eintrag löschen","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"tooltip.entry.markAsDeleted","bundleName":"I18nResources","translation":"Mark entry as deleted","translationDE":"Eintrag als gelöscht markieren","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"tooltip.entry.undelete","bundleName":"I18nResources","translation":"Undelete entry","translationDE":"Eintrag wiederherstellen","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"tooltip.export.excel","bundleName":"I18nResources","translation":"Export as Excel file","translationDE":"Export im Excelformat","usedInClasses":["org.projectforge.web.fibu.AuftragListPage","org.projectforge.web.fibu.Kost1ListPage","org.projectforge.web.fibu.Kost2ListPage","org.projectforge.web.timesheet.TimesheetListPage","org.projectforge.web.wicket.AbstractListPage"],"usedInFiles":[]}, {"i18nKey":"tooltip.export.pdf","bundleName":"I18nResources","translation":"Export as pdf","translationDE":"Export als pdf","usedInClasses":["org.projectforge.web.timesheet.TimesheetListPage"],"usedInFiles":[]}, {"i18nKey":"tooltip.hideBirthdays","bundleName":"I18nResources","translation":"Hide birthdays","translationDE":"Geburtstage ausblenden","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"tooltip.jiraSupport.field.content","bundleName":"I18nResources","translation":"JIRA issues are supported by this field, therefore JIRA issues such as PROJECTFORGE-123 will be displayed as a link to JIRA.","translationDE":"Es werden JIRA-Issues von diesem Feld unterstützt, d. h. JIRA-Issues der Form PROJECTFORGE-123 werden als JIRA-Link angezeigt.","usedInClasses":["org.projectforge.ui.JiraSupport","org.projectforge.web.wicket.WicketUtils"],"usedInFiles":[]}, + {"i18nKey":"tooltip.jiraSupport.field.content","bundleName":"I18nResources","translation":"JIRA issues are supported by this field, therefore JIRA issues such as PROJECTFORGE-123 will be displayed as a link to JIRA.","translationDE":"Es werden JIRA-Issues von diesem Feld unterstützt, d. h. JIRA-Issues der Form PROJECTFORGE-123 werden als JIRA-Link angezeigt.","usedInClasses":["org.projectforge.ui.JiraSupport","org.projectforge.web.wicket.WicketUtils"],"usedInFiles":[]}, {"i18nKey":"tooltip.jiraSupport.field.title","bundleName":"I18nResources","translation":"JIRA-Support","translationDE":"JIRA-Support","usedInClasses":["org.projectforge.web.wicket.WicketUtils"],"usedInFiles":[]}, {"i18nKey":"tooltip.lucene.link","bundleName":"I18nResources","translation":"Link to howto of lucene queries.","translationDE":"Link zur Lucene-Abfrage-Syntax.","usedInClasses":["org.projectforge.web.task.TaskTreeForm","org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]}, - {"i18nKey":"tooltip.popups.mustBeAllowed","bundleName":"I18nResources","translation":"Please note: pop-ups must be allowed in your browser for this feature.","translationDE":"Bitte beachten: Für diese Funktion müssen Popups in Ihrem Browser erlaubt sein.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"tooltip.popups.mustBeAllowed","bundleName":"I18nResources","translation":"Please note: pop-ups must be allowed in your browser for this feature.","translationDE":"Bitte beachten: Für diese Funktion müssen Popups in Ihrem Browser erlaubt sein.","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"tooltip.reload","bundleName":"I18nResources","translation":"Reload","translationDE":"Neu laden","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"tooltip.selectBirthday","bundleName":"I18nResources","translation":"Select birthday","translationDE":"Geburtstag wählen","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"tooltip.selectDate","bundleName":"I18nResources","translation":"Select date","translationDE":"Datum wählen","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"tooltip.selectDateOrPeriod","bundleName":"I18nResources","translation":"Select date or time period","translationDE":"Datum oder Zeitraum wählen","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"tooltip.selectGroup","bundleName":"I18nResources","translation":"Select group","translationDE":"Gruppe wählen","usedInClasses":["org.projectforge.web.user.GroupSelectPanel","org.projectforge.web.user.NewGroupSelectPanel"],"usedInFiles":[]}, + {"i18nKey":"tooltip.selectBirthday","bundleName":"I18nResources","translation":"Select birthday","translationDE":"Geburtstag wählen","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"tooltip.selectDate","bundleName":"I18nResources","translation":"Select date","translationDE":"Datum wählen","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"tooltip.selectDateOrPeriod","bundleName":"I18nResources","translation":"Select date or time period","translationDE":"Datum oder Zeitraum wählen","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"tooltip.selectGroup","bundleName":"I18nResources","translation":"Select group","translationDE":"Gruppe wählen","usedInClasses":["org.projectforge.web.user.GroupSelectPanel","org.projectforge.web.user.NewGroupSelectPanel"],"usedInFiles":[]}, {"i18nKey":"tooltip.selectMe","bundleName":"I18nResources","translation":"You are great!","translationDE":"You are great!","usedInClasses":["org.projectforge.plugins.datatransfer.rest.DataTransferPersonalBoxPageRest","org.projectforge.rest.calendar.CalendarFilterServicesRest","org.projectforge.rest.core.AbstractPagesRest","org.projectforge.web.user.UserSelectPanel"],"usedInFiles":[]}, - {"i18nKey":"tooltip.selectTask","bundleName":"I18nResources","translation":"Select structure element","translationDE":"Strukturelement wählen","usedInClasses":["org.projectforge.web.gantt.GanttChartEditTreeTablePanel","org.projectforge.web.task.TaskSelectPanel"],"usedInFiles":[]}, - {"i18nKey":"tooltip.selectUser","bundleName":"I18nResources","translation":"Select user","translationDE":"Benutzer:in wählen","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"tooltip.unassign","bundleName":"I18nResources","translation":"Unassign selected elements","translationDE":"Zuweisung für selektierte Einträge aufheben","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"tooltip.selectTask","bundleName":"I18nResources","translation":"Select structure element","translationDE":"Strukturelement wählen","usedInClasses":["org.projectforge.web.gantt.GanttChartEditTreeTablePanel","org.projectforge.web.task.TaskSelectPanel"],"usedInFiles":[]}, + {"i18nKey":"tooltip.selectUser","bundleName":"I18nResources","translation":"Select user","translationDE":"Benutzer:in wählen","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"tooltip.unassign","bundleName":"I18nResources","translation":"Unassign selected elements","translationDE":"Zuweisung für selektierte Einträge aufheben","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"tooltip.unselect","bundleName":"I18nResources","translation":"Unselect item","translationDE":"Auswahl aufheben","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"tooltip.unselectBirthday","bundleName":"I18nResources","translation":"Unselect birthday","translationDE":"Geburtstag löschen","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"tooltip.unselectBirthday","bundleName":"I18nResources","translation":"Unselect birthday","translationDE":"Geburtstag löschen","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"tooltip.unselectDate","bundleName":"I18nResources","translation":"Unselect date","translationDE":"Datumswahl aufheben","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"tooltip.unselectGroup","bundleName":"I18nResources","translation":"Unselect group","translationDE":"Gruppenauswahl aufheben","usedInClasses":["org.projectforge.web.user.GroupSelectPanel","org.projectforge.web.user.NewGroupSelectPanel"],"usedInFiles":[]}, {"i18nKey":"tooltip.unselectTask","bundleName":"I18nResources","translation":"Unselect structure element","translationDE":"Strukturelementwahl aufheben","usedInClasses":["org.projectforge.web.gantt.GanttChartEditTreeTablePanel","org.projectforge.web.task.TaskSelectPanel"],"usedInFiles":[]}, {"i18nKey":"tooltip.unselectUser","bundleName":"I18nResources","translation":"Unselect user","translationDE":"Benutzer:innenwahl aufheben","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"totalSum","bundleName":"I18nResources","translation":"Total sum","translationDE":"Gesamtsumme","usedInClasses":["org.projectforge.plugins.liquidityplanning.LiquidityEntryListForm"],"usedInFiles":[]}, - {"i18nKey":"tutorial.expectedGroupNotFound","bundleName":"I18nResources","translation":"The expected group ''{0}'' doesn''t exist. Can''t process this tutorial request.","translationDE":"Die benötigte Gruppe ''{0}'' ist noch nicht angelegt. Die Tutorialanfrage kann deshalb nicht bearbeitet werden.","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"tutorial.expectedTaskNotFound","bundleName":"I18nResources","translation":"The expected task ''{0}'' doesn''t exist. Can''t process this tutorial request.","translationDE":"Das benötigte Strukturelement ''{0}'' ist noch nicht angelegt. Die Tutorialanfrage kann deshalb nicht bearbeitet werden.","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"tutorial.expectedUserNotFound","bundleName":"I18nResources","translation":"The expected user ''{0}'' doesn''t exist. Can''t process this tutorial request.","translationDE":"Der benötigte Benutzer ''{0}'' ist noch nicht angelegt. Die Tutorialanfrage kann deshalb nicht bearbeitet werden.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"tutorial.expectedGroupNotFound","bundleName":"I18nResources","translation":"The expected group ''{0}'' doesn''t exist. Can''t process this tutorial request.","translationDE":"Die benötigte Gruppe ''{0}'' ist noch nicht angelegt. Die Tutorialanfrage kann deshalb nicht bearbeitet werden.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"tutorial.expectedTaskNotFound","bundleName":"I18nResources","translation":"The expected task ''{0}'' doesn''t exist. Can''t process this tutorial request.","translationDE":"Das benötigte Strukturelement ''{0}'' ist noch nicht angelegt. Die Tutorialanfrage kann deshalb nicht bearbeitet werden.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"tutorial.expectedUserNotFound","bundleName":"I18nResources","translation":"The expected user ''{0}'' doesn''t exist. Can''t process this tutorial request.","translationDE":"Der benötigte Benutzer ''{0}'' ist noch nicht angelegt. Die Tutorialanfrage kann deshalb nicht bearbeitet werden.","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"tutorial.objectAlreadyCreated","bundleName":"I18nResources","translation":"Can't process this tutorial request, the requested object does already exist.","translationDE":"Die Tutorialanfrage kann nicht bearbeitet werden, da das angefragte Objekt bereits existiert.","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"tutorial.unknown","bundleName":"I18nResources","translation":"Can't process this tutorial request.","translationDE":"Die Tutorialanfrage kann nicht bearbeitet werden.","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"unassign","bundleName":"I18nResources","translation":"Unassign","translationDE":"Entfernen","usedInClasses":[],"usedInFiles":[]}, @@ -2446,47 +2500,47 @@ {"i18nKey":"updateAndNext","bundleName":"I18nResources","translation":"Update and next","translationDE":"Ändern und nächster","usedInClasses":["org.projectforge.web.wicket.AbstractEditForm"],"usedInFiles":[]}, {"i18nKey":"upload","bundleName":"I18nResources","translation":"Upload","translationDE":"Hochladen","usedInClasses":["org.projectforge.framework.jcr.AttachmentsEventType","org.projectforge.plugins.eed.wicket.EmployeeBillingImportForm","org.projectforge.plugins.eed.wicket.EmployeeSalaryImportForm","org.projectforge.web.teamcal.event.importics.TeamCalImportForm","org.projectforge.web.wicket.flowlayout.IconType"],"usedInFiles":["./projectforge-wicket/src/main/java/org/projectforge/web/admin/SetupPage.html"]}, {"i18nKey":"uptodate","bundleName":"I18nResources","translation":"up-to-date","translationDE":"aktuell","usedInClasses":["org.projectforge.business.address.AddressStatus","org.projectforge.favorites.Favorites","org.projectforge.plugins.licensemanagement.LicenseStatus","org.projectforge.web.address.AddressListForm"],"usedInFiles":[]}, - {"i18nKey":"user","bundleName":"I18nResources","translation":"User","translationDE":"Benutzer:in","usedInClasses":["org.projectforge.business.humanresources.HRPlanningDao","org.projectforge.business.humanresources.HRPlanningEntryDao","org.projectforge.business.scripting.ScriptParameterType","org.projectforge.business.timesheet.TimesheetDO","org.projectforge.business.timesheet.TimesheetDao","org.projectforge.business.user.UserFavorite","org.projectforge.business.user.UserPrefDOXmlDumpHook","org.projectforge.business.user.UserRightDao","org.projectforge.framework.access.AccessException","org.projectforge.framework.jobs.AbstractJob","org.projectforge.framework.persistence.DaoConst","org.projectforge.framework.persistence.database.json.DatabaseWriter","org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.framework.persistence.user.entities.UserAuthenticationsDO","org.projectforge.framework.persistence.user.entities.UserPasswordDO","org.projectforge.framework.persistence.user.entities.UserPrefXmlBeforePersistListener","org.projectforge.menu.builder.MenuItemDefId","org.projectforge.plugins.datatransfer.rest.DataTransferPersonalBoxPageRest","org.projectforge.plugins.eed.excelimport.EmployeeBillingExcelImporter","org.projectforge.plugins.eed.wicket.EmployeeListEditPage","org.projectforge.registry.Registry","org.projectforge.renderer.custom.MicromataFormatter","org.projectforge.rest.CardDAVInfoPageRest","org.projectforge.rest.TimesheetMultiSelectedPageRest","org.projectforge.rest.TimesheetPagesRest","org.projectforge.rest.admin.LogViewerPageRest","org.projectforge.rest.fibu.EmployeePagesRest","org.projectforge.rest.hr.HRPlanningPagesRest","org.projectforge.rest.pub.CalendarSubscriptionServiceRest","org.projectforge.security.dto.WebAuthnPublicKeyCredentialCreationOptions","org.projectforge.ui.AutoCompletion","org.projectforge.web.access.AccessListForm","org.projectforge.web.calendar.CalendarPageSupport","org.projectforge.web.core.NavTopPanel","org.projectforge.web.fibu.AuftragListForm","org.projectforge.web.fibu.EmployeeEditForm","org.projectforge.web.fibu.MonthlyEmployeeReportForm","org.projectforge.web.fibu.MonthlyEmployeeReportPage","org.projectforge.web.humanresources.HRListPage","org.projectforge.web.humanresources.HRListResourceLinkPanel","org.projectforge.web.humanresources.HRPlanningEditForm","org.projectforge.web.humanresources.HRPlanningListForm","org.projectforge.web.rest.RestCalendarSubscriptionUserFilter","org.projectforge.web.timesheet.TimesheetEditForm","org.projectforge.web.timesheet.TimesheetEditPage","org.projectforge.web.timesheet.TimesheetListForm","org.projectforge.web.timesheet.TimesheetListPage","org.projectforge.web.user.AttendeeWicketProvider","org.projectforge.web.user.UserEditForm","org.projectforge.web.user.UserEditPage","org.projectforge.web.user.UserListPage","org.projectforge.web.user.UserPrefEditForm","org.projectforge.web.user.UserPrefListPage","org.projectforge.web.wicket.AbstractEditPage","org.projectforge.web.wicket.AbstractListForm","org.projectforge.web.wicket.EditPageSupport","org.projectforge.web.wicket.flowlayout.IconType"],"usedInFiles":["./projectforge-business/src/main/resources/mail/mailHistoryTable.html","./projectforge-common/src/main/kotlin/org/projectforge/common/logging/LogConstants.kt","./projectforge-wicket/src/main/java/org/projectforge/web/core/NavTopPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/humanresources/HRListResourceLinkPanel.html"]}, - {"i18nKey":"user.My2FA.expired","bundleName":"I18nResources","translation":"For the requested action a 2FA is required not older than {0}.","translationDE":"Für die angeforderte Aktion ist eine (erneute) Zwei-Faktor-Authentifizierung erforderlich, die nicht älter als {0} ist.","usedInClasses":["org.projectforge.rest.my2fa.My2FAPageRest"],"usedInFiles":[]}, + {"i18nKey":"user","bundleName":"I18nResources","translation":"User","translationDE":"Benutzer:in","usedInClasses":["org.projectforge.business.humanresources.HRPlanningDao","org.projectforge.business.humanresources.HRPlanningEntryDao","org.projectforge.business.scripting.ScriptParameterType","org.projectforge.business.timesheet.TimesheetDO","org.projectforge.business.timesheet.TimesheetDao","org.projectforge.business.user.UserFavorite","org.projectforge.business.user.UserPrefDOXmlDumpHook","org.projectforge.business.user.UserRightDao","org.projectforge.framework.access.AccessException","org.projectforge.framework.jobs.AbstractJob","org.projectforge.framework.persistence.DaoConst","org.projectforge.framework.persistence.database.json.DatabaseWriter","org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.framework.persistence.user.entities.UserAuthenticationsDO","org.projectforge.framework.persistence.user.entities.UserPasswordDO","org.projectforge.framework.persistence.user.entities.UserPrefXmlBeforePersistListener","org.projectforge.menu.builder.MenuItemDefId","org.projectforge.plugins.datatransfer.rest.DataTransferPersonalBoxPageRest","org.projectforge.plugins.eed.excelimport.EmployeeBillingExcelImporter","org.projectforge.plugins.eed.wicket.EmployeeListEditPage","org.projectforge.registry.Registry","org.projectforge.renderer.custom.MicromataFormatter","org.projectforge.rest.CardDAVInfoPageRest","org.projectforge.rest.TimesheetMultiSelectedPageRest","org.projectforge.rest.TimesheetPagesRest","org.projectforge.rest.admin.LogViewerPageRest","org.projectforge.rest.fibu.EmployeePagesRest","org.projectforge.rest.hr.HRPlanningPagesRest","org.projectforge.rest.poll.ResponsePageRest","org.projectforge.rest.pub.CalendarSubscriptionServiceRest","org.projectforge.security.dto.WebAuthnPublicKeyCredentialCreationOptions","org.projectforge.ui.AutoCompletion","org.projectforge.web.access.AccessListForm","org.projectforge.web.calendar.CalendarPageSupport","org.projectforge.web.core.NavTopPanel","org.projectforge.web.fibu.AuftragListForm","org.projectforge.web.fibu.EmployeeEditForm","org.projectforge.web.fibu.MonthlyEmployeeReportForm","org.projectforge.web.fibu.MonthlyEmployeeReportPage","org.projectforge.web.humanresources.HRListPage","org.projectforge.web.humanresources.HRListResourceLinkPanel","org.projectforge.web.humanresources.HRPlanningEditForm","org.projectforge.web.humanresources.HRPlanningListForm","org.projectforge.web.rest.RestCalendarSubscriptionUserFilter","org.projectforge.web.timesheet.TimesheetEditForm","org.projectforge.web.timesheet.TimesheetEditPage","org.projectforge.web.timesheet.TimesheetListForm","org.projectforge.web.timesheet.TimesheetListPage","org.projectforge.web.user.AttendeeWicketProvider","org.projectforge.web.user.UserEditForm","org.projectforge.web.user.UserEditPage","org.projectforge.web.user.UserListPage","org.projectforge.web.user.UserPrefEditForm","org.projectforge.web.user.UserPrefListPage","org.projectforge.web.wicket.AbstractEditPage","org.projectforge.web.wicket.AbstractListForm","org.projectforge.web.wicket.EditPageSupport","org.projectforge.web.wicket.flowlayout.IconType"],"usedInFiles":["./projectforge-business/src/main/resources/mail/mailHistoryTable.html","./projectforge-common/src/main/kotlin/org/projectforge/common/logging/LogConstants.kt","./projectforge-wicket/src/main/java/org/projectforge/web/core/NavTopPanel.html","./projectforge-wicket/src/main/java/org/projectforge/web/humanresources/HRListResourceLinkPanel.html"]}, + {"i18nKey":"user.My2FA.expired","bundleName":"I18nResources","translation":"For the requested action a 2FA is required not older than {0}.","translationDE":"Für die angeforderte Aktion ist eine (erneute) Zwei-Faktor-Authentifizierung erforderlich, die nicht älter als {0} ist.","usedInClasses":["org.projectforge.rest.my2fa.My2FAPageRest"],"usedInFiles":[]}, {"i18nKey":"user.My2FA.required","bundleName":"I18nResources","translation":"A (new) two factor authentication is required for proceeding.","translationDE":"Eine (erneute) Zwei-Faktor-Authentifizierung ist erforderlich.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest","org.projectforge.security.WebAuthnServicesRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FA.required.extended","bundleName":"I18nResources","translation":"A (new) two factor authentication is required for proceeding due to security reasons. You may request a code (see link below code field) or, if configured, use Your authenticator app.","translationDE":"Eine (erneute) Zwei-Faktor-Authentifizierung ist aus Sicherheitsgründen erforderlich. Du kannst hierzu einen Code anfordern (s. Link unterhalb des Code-Feldes) oder, falls konfiguriert, deine Authenticator-App benutzen.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FA.setup.authenticator.info","bundleName":"I18nResources","translation":"### Setup your smart phone\n\n1. Scan this barcode in Your Authenticator-App.\n2. Done.\n\nEvery time ProjectForge requests a code, enter the displayed code in your Authenticator app.\n\nYou may setup others Authenticator apps on different devices as a backup, if you want.\n\nThe code displayed in your Authenticator app is valid for up to 30 seconds after disappearing in your app.","translationDE":"Smartphone einrichten\n\n1. Einfach den Barcode in der Authenticator-App scannen.\n2. Fertig.\n\nJedesmal, wenn ProjectForge nach einem Code fragt, einfach den in der Authenticator-App eingeblendeten Code verwenden.\n\nEs können mehrere Authenticator-Apps gleichzeitig (auch als Backup) genutzt werden.\n\nCodes aus der Authenticator-App sind noch für ca. 30 Sekunden gültig, nachdem sie in der App erneuert wurden.\n\nDiese Barcode-Ansicht ist für ca. 10 Minuten einsehbar. Anschließend wird für die erneute Anzeige eine 2FA-Authentifizierung benötigt.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FA.setup.authenticator.intro","bundleName":"I18nResources","translation":"Please, use your 2FA Authenticator app, such as Microsoft or Google Authenticator as a second factor.","translationDE":"Für die Zwei-Faktor-Authentifzierung sollte eine Authenticator-App eingerichtet werden (z. B. Microsoft-, Google-Authenticator oder Fortitoken).","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FA.required.extended","bundleName":"I18nResources","translation":"A (new) two factor authentication is required for proceeding due to security reasons. You may request a code (see link below code field) or, if configured, use Your authenticator app.","translationDE":"Eine (erneute) Zwei-Faktor-Authentifizierung ist aus Sicherheitsgründen erforderlich. Du kannst hierzu einen Code anfordern (s. Link unterhalb des Code-Feldes) oder, falls konfiguriert, deine Authenticator-App benutzen.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FA.setup.authenticator.info","bundleName":"I18nResources","translation":"### Setup your smart phone\n\n1. Scan this barcode in Your Authenticator-App.\n2. Done.\n\nEvery time ProjectForge requests a code, enter the displayed code in your Authenticator app.\n\nYou may setup others Authenticator apps on different devices as a backup, if you want.\n\nThe code displayed in your Authenticator app is valid for up to 30 seconds after disappearing in your app.","translationDE":"Smartphone einrichten\n\n1. Einfach den Barcode in der Authenticator-App scannen.\n2. Fertig.\n\nJedesmal, wenn ProjectForge nach einem Code fragt, einfach den in der Authenticator-App eingeblendeten Code verwenden.\n\nEs können mehrere Authenticator-Apps gleichzeitig (auch als Backup) genutzt werden.\n\nCodes aus der Authenticator-App sind noch für ca. 30 Sekunden gültig, nachdem sie in der App erneuert wurden.\n\nDiese Barcode-Ansicht ist für ca. 10 Minuten einsehbar. Anschließend wird für die erneute Anzeige eine 2FA-Authentifizierung benötigt.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FA.setup.authenticator.intro","bundleName":"I18nResources","translation":"Please, use your 2FA Authenticator app, such as Microsoft or Google Authenticator as a second factor.","translationDE":"Für die Zwei-Faktor-Authentifzierung sollte eine Authenticator-App eingerichtet werden (z. B. Microsoft-, Google-Authenticator oder Fortitoken).","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, {"i18nKey":"user.My2FA.setup.authenticator.title","bundleName":"I18nResources","translation":"Authenticator apps","translationDE":"Authenticator-Apps","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FA.setup.authenticatorKey","bundleName":"I18nResources","translation":"Authenticator key","translationDE":"Authenticator-Schlüssel","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FA.setup.check.fail","bundleName":"I18nResources","translation":"Code check failed.","translationDE":"Code-Prüfung fehlgeschlagen.","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FA.setup.check.success","bundleName":"I18nResources","translation":"Code checked successfully.","translationDE":"Code-Prüfung erfolgreich.","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FA.setup.authenticatorKey","bundleName":"I18nResources","translation":"Authenticator key","translationDE":"Authenticator-Schlüssel","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FA.setup.check.fail","bundleName":"I18nResources","translation":"Code check failed.","translationDE":"Code-Prüfung fehlgeschlagen.","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FA.setup.check.success","bundleName":"I18nResources","translation":"Code checked successfully.","translationDE":"Code-Prüfung erfolgreich.","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, {"i18nKey":"user.My2FA.setup.disableAuthenticatorApp","bundleName":"I18nResources","translation":"Disable Authenticator app","translationDE":"Authenticator-App entfernen","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FA.setup.disableAuthenticatorApp.confirmMessage","bundleName":"I18nResources","translation":"Do you want to disable or renew the token for Authenticator apps now? All Authenticator apps will be invalidated.","translationDE":"Soll die Authentificator-App nun entfernt bzw. erneuert werden? Alle bisherhigen Authentificator-Apps werden dann ungültig.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FA.setup.disableAuthenticatorApp.info","bundleName":"I18nResources","translation":"Click this button to disable or renew Authentificator apps. A two-factor authentication not older than 2 minutes is required.","translationDE":"Diesen Knopf klicken, um die Authentificator-App zu entfernen oder zu erneuern. Für diese Funktion wird selber ein 2. FAktor benötigt, der nicht älter ist als 2 Minuten.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FA.setup.disableAuthenticatorApp.confirmMessage","bundleName":"I18nResources","translation":"Do you want to disable or renew the token for Authenticator apps now? All Authenticator apps will be invalidated.","translationDE":"Soll die Authentificator-App nun entfernt bzw. erneuert werden? Alle bisherhigen Authentificator-Apps werden dann ungültig.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FA.setup.disableAuthenticatorApp.info","bundleName":"I18nResources","translation":"Click this button to disable or renew Authentificator apps. A two-factor authentication not older than 2 minutes is required.","translationDE":"Diesen Knopf klicken, um die Authentificator-App zu entfernen oder zu erneuern. Für diese Funktion wird selber ein 2. FAktor benötigt, der nicht älter ist als 2 Minuten.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, {"i18nKey":"user.My2FA.setup.enableAuthenticatorApp","bundleName":"I18nResources","translation":"Enable Authenticator app","translationDE":"Authentificator-Apps einrichten","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FA.setup.enableAuthenticatorApp.confirmMessage","bundleName":"I18nResources","translation":"Do you want to setup an Authenticator apps? Afterwards a second factor is required on login. This operation is undoable.","translationDE":"Soll eine Authenticator-App nun eingerichtet werden? Anschließend wird für eine Anmeldung ein 2. Faktor benötigt. Diese Operation kann rückgängig gemacht werden.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FA.setup.enableAuthenticatorApp.info","bundleName":"I18nResources","translation":"Click this button to setup an Authentificator app.","translationDE":"Hierüber kann eine Authenticator-App eingerichtet werden.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FA.setup.info.1","bundleName":"I18nResources","translation":"### Second factor authentication (2FA)\n\n* ProjectForge supports 2FA for more security. Authenticator apps, mobile phones if texting is configured and/or e-mail are supported.\n* Every time ProjectForge requests a code, you may enter it from your Authenticator app or request a one-time-password by e-mail or as text message.\n\nPlease use the stay-logged-in functionality on login to prevent annoying 2FA requests!","translationDE":"### Zwei-Faktor-Authentifizierung (2FA)\n\n* ProjectForge unterstützt 2FA für eine erhöhte Sicherheit. Authenticator-Apps, SMS (wenn konfiguriert) und/oder E-Mails werden als 2. Faktor unterstützt.\n* Immer, wenn ProjectForge einen Code anfordert, kann ein Code aus der Authenticator-App eingegeben werden oder ein Einmalpasswort per E-Mail oder als SMS angefordert werden.\n\nWenn die Angemeldet-Bleiben-Funktion bei der Anmeldung verwendet wird, kann eine unnötig häufige Code-Abfrage vermieden werden!","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FA.setup.info.2","bundleName":"I18nResources","translation":"It's highly recommended to configure a second factor via Authenticator App and/or WebAuthn (e. g. Fido2) and, if available a mobile phone number for a second factor via text messages.\n\nSome security relevant functionalities such as password reset are only available, if a second factor is configured (Authenticator-App, mobile phone or WebAuthn).","translationDE":"Es wird dringend empfohlen, einen zweiten Faktor über eine Authenticator-App und/oder WebAuthn (z. B. Fidu2) und, falls verfügbar, auch eine Mobilfunknummer zum Anfordern von Codes per SMS zu konfiguieren.\n\nEinige Sicherheitsfunktionen, wie z. B. ein Passwort-Reset, steht nur zur Verfügung, wenn mindestens ein 2. Faktor (Authenticator-App, SMS oder WebAuthn) konfiguriert ist.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FA.setup.info.3","bundleName":"I18nResources","translation":"Feel free to test 2FA codes here. Modifications on this setup pages need a current 2FA authentification as well.","translationDE":"Hier können 2FA-Codes getestet werden. Außerdem benötigen Änderungen auf dieser Seite eine aktuelle 2FA-Prüfung.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FA.setup.enableAuthenticatorApp.confirmMessage","bundleName":"I18nResources","translation":"Do you want to setup an Authenticator apps? Afterwards a second factor is required on login. This operation is undoable.","translationDE":"Soll eine Authenticator-App nun eingerichtet werden? Anschließend wird für eine Anmeldung ein 2. Faktor benötigt. Diese Operation kann rückgängig gemacht werden.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FA.setup.enableAuthenticatorApp.info","bundleName":"I18nResources","translation":"Click this button to setup an Authentificator app.","translationDE":"Hierüber kann eine Authenticator-App eingerichtet werden.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FA.setup.info.1","bundleName":"I18nResources","translation":"### Second factor authentication (2FA)\n\n* ProjectForge supports 2FA for more security. Authenticator apps, mobile phones if texting is configured and/or e-mail are supported.\n* Every time ProjectForge requests a code, you may enter it from your Authenticator app or request a one-time-password by e-mail or as text message.\n\nPlease use the stay-logged-in functionality on login to prevent annoying 2FA requests!","translationDE":"### Zwei-Faktor-Authentifizierung (2FA)\n\n* ProjectForge unterstützt 2FA für eine erhöhte Sicherheit. Authenticator-Apps, SMS (wenn konfiguriert) und/oder E-Mails werden als 2. Faktor unterstützt.\n* Immer, wenn ProjectForge einen Code anfordert, kann ein Code aus der Authenticator-App eingegeben werden oder ein Einmalpasswort per E-Mail oder als SMS angefordert werden.\n\nWenn die Angemeldet-Bleiben-Funktion bei der Anmeldung verwendet wird, kann eine unnötig häufige Code-Abfrage vermieden werden!","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FA.setup.info.2","bundleName":"I18nResources","translation":"It's highly recommended to configure a second factor via Authenticator App and/or WebAuthn (e. g. Fido2) and, if available a mobile phone number for a second factor via text messages.\n\nSome security relevant functionalities such as password reset are only available, if a second factor is configured (Authenticator-App, mobile phone or WebAuthn).","translationDE":"Es wird dringend empfohlen, einen zweiten Faktor über eine Authenticator-App und/oder WebAuthn (z. B. Fidu2) und, falls verfügbar, auch eine Mobilfunknummer zum Anfordern von Codes per SMS zu konfiguieren.\n\nEinige Sicherheitsfunktionen, wie z. B. ein Passwort-Reset, steht nur zur Verfügung, wenn mindestens ein 2. Faktor (Authenticator-App, SMS oder WebAuthn) konfiguriert ist.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FA.setup.info.3","bundleName":"I18nResources","translation":"Feel free to test 2FA codes here. Modifications on this setup pages need a current 2FA authentification as well.","translationDE":"Hier können 2FA-Codes getestet werden. Außerdem benötigen Änderungen auf dieser Seite eine aktuelle 2FA-Prüfung.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, {"i18nKey":"user.My2FA.setup.info.title","bundleName":"I18nResources","translation":"Two factor authentication","translationDE":"Zweifaktorauthentifizierung","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, {"i18nKey":"user.My2FA.setup.showAuthenticatorKey","bundleName":"I18nResources","translation":"Show authenticator key","translationDE":"Authenticator-Zugang anzeigen","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, {"i18nKey":"user.My2FA.setup.sms.info","bundleName":"I18nResources","translation":"You may configure here your mobile phone number for receiving one time passwords on demand as text message.","translationDE":"Hier kann eine Mobilfunknummer hinterlegt werden, um einen 2. Faktor per SMS anzufordern.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, {"i18nKey":"user.My2FA.setup.sms.info.title","bundleName":"I18nResources","translation":"SMS configuration","translationDE":"SMS-Konfiguration","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FA.setup.sms.mobileNumberRecommended","bundleName":"I18nResources","translation":"It's highly recommended to configure Your mobile number here as a second factor.\n\nYou may get a second factor as a text message as an alternative.\n\nIf you want to change Your mobile number, a (new) 2FA is required (see above).","translationDE":"Es wird dringend empfohlen, hier die eigene Mobilfunknummer anzugeben.\n\nÜber das Mobiltelefon kann ein zweiter Faktor als SMS alternativ angefordert werden.\n\nWenn die Mobilfunknummer geändert werden soll, wird eine (neue) Zweifaktorauthentifizierung benötigt (siehe oben).","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FA.setup.sms.mobileNumberRecommended","bundleName":"I18nResources","translation":"It's highly recommended to configure Your mobile number here as a second factor.\n\nYou may get a second factor as a text message as an alternative.\n\nIf you want to change Your mobile number, a (new) 2FA is required (see above).","translationDE":"Es wird dringend empfohlen, hier die eigene Mobilfunknummer anzugeben.\n\nÜber das Mobiltelefon kann ein zweiter Faktor als SMS alternativ angefordert werden.\n\nWenn die Mobilfunknummer geändert werden soll, wird eine (neue) Zweifaktorauthentifizierung benötigt (siehe oben).","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, {"i18nKey":"user.My2FA.setup.title","bundleName":"I18nResources","translation":"Setup of two-factor-authentication (2FA)","translationDE":"Zwei-Faktor-Authentifizierung (2FA) einrichten","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FACode.authentification.info","bundleName":"I18nResources","translation":"A second factor is required for proceeding. Please enter any 2nd factor (e. g. OTP) you have (requested): from Your Authenticator-App or received by SMS or mail or you may use any WebAuthn token you have registered.","translationDE":"Ein zweiter Faktor ist nun erforderlich. Hier kann ein 2. Faktor (z. B. OTP-Einmalpasswort) eingegeben werden: Aus einer Authenticator-App, ein angeforderter Code per SMS oder E-Mail oder über einen registrierten WebAuthn-Token.","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FACode.authentification.info","bundleName":"I18nResources","translation":"A second factor is required for proceeding. Please enter any 2nd factor (e. g. OTP) you have (requested): from Your Authenticator-App or received by SMS or mail or you may use any WebAuthn token you have registered.","translationDE":"Ein zweiter Faktor ist nun erforderlich. Hier kann ein 2. Faktor (z. B. OTP-Einmalpasswort) eingegeben werden: Aus einer Authenticator-App, ein angeforderter Code per SMS oder E-Mail oder über einen registrierten WebAuthn-Token.","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, {"i18nKey":"user.My2FACode.code","bundleName":"I18nResources","translation":"Code","translationDE":"Code","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest","org.projectforge.security.My2FAData"],"usedInFiles":[]}, {"i18nKey":"user.My2FACode.code.info","bundleName":"I18nResources","translation":"Code from your authenticator app or the one time password (OTP) you received by e-mail of text message.","translationDE":"Code aus der Authenticator-App oder der Code (Einmalpasswort), welches per E-Mail oder SMS versendet wurde.","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest","org.projectforge.security.My2FAData"],"usedInFiles":[]}, - {"i18nKey":"user.My2FACode.code.validate","bundleName":"I18nResources","translation":"Validate","translationDE":"Prüfen","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FACode.error.timePenalty.message","bundleName":"I18nResources","translation":"You have been blocked until {0} ({1}) after {2} failed code checks. Please note, that your account might be deactivated after {3} failed OTP checks.","translationDE":"Die Funktion ist aus Sicherheitsgründen blockiert bis {0} ({1}) nach {2} Fehlversuchen. Bitte beachten, dass dieser Account nach {3} Fehlversuchen gesperrt wird.","usedInClasses":["org.projectforge.security.My2FABruteForceProtection"],"usedInFiles":[]}, - {"i18nKey":"user.My2FACode.error.validation","bundleName":"I18nResources","translation":"Code not correct or two-factor authentication not configured.","translationDE":"Code nicht gültig oder die Zweifaktor-Authentifizierung ist noch nicht eingerichtet.","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FACode.code.validate","bundleName":"I18nResources","translation":"Validate","translationDE":"Prüfen","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FACode.error.timePenalty.message","bundleName":"I18nResources","translation":"You have been blocked until {0} ({1}) after {2} failed code checks. Please note, that your account might be deactivated after {3} failed OTP checks.","translationDE":"Die Funktion ist aus Sicherheitsgründen blockiert bis {0} ({1}) nach {2} Fehlversuchen. Bitte beachten, dass dieser Account nach {3} Fehlversuchen gesperrt wird.","usedInClasses":["org.projectforge.security.My2FABruteForceProtection"],"usedInFiles":[]}, + {"i18nKey":"user.My2FACode.error.validation","bundleName":"I18nResources","translation":"Code not correct or two-factor authentication not configured.","translationDE":"Code nicht gültig oder die Zweifaktor-Authentifizierung ist noch nicht eingerichtet.","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, {"i18nKey":"user.My2FACode.lastSuccessful2FA","bundleName":"I18nResources","translation":"Last successful 2 FA","translationDE":"Letzte erfolgreiche 2. Faktorauthentifizierung","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FACode.password.info","bundleName":"I18nResources","translation":"The password is only needed as an additional factor if you received the code via e-mail.","translationDE":"Das Password wird nur beim Versenden des Einmalpassworts per E-Mail aus Sicherheitsgründen verlangt.","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FACode.password.wrong","bundleName":"I18nResources","translation":"Password check failed.","translationDE":"Passwortprüfung fehlgeschlagen.","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FACode.password.info","bundleName":"I18nResources","translation":"The password is only needed as an additional factor if you received the code via e-mail.","translationDE":"Das Password wird nur beim Versenden des Einmalpassworts per E-Mail aus Sicherheitsgründen verlangt.","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FACode.password.wrong","bundleName":"I18nResources","translation":"Password check failed.","translationDE":"Passwortprüfung fehlgeschlagen.","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, {"i18nKey":"user.My2FACode.sendCode.mail","bundleName":"I18nResources","translation":"Request mail code","translationDE":"E-Mail-Code anfordern","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FACode.sendCode.mail.info","bundleName":"I18nResources","translation":"Send code as e-mail. The code will be valid up to 2 minutes and your password is required when validating the sent code.","translationDE":"Code per E-Mail anfordern. Dieser Code ist bis zu 2 Minuten gültig.","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FACode.sendCode.mail.info","bundleName":"I18nResources","translation":"Send code as e-mail. The code will be valid up to 2 minutes and your password is required when validating the sent code.","translationDE":"Code per E-Mail anfordern. Dieser Code ist bis zu 2 Minuten gültig.","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, {"i18nKey":"user.My2FACode.sendCode.mail.message","bundleName":"I18nResources","translation":"Your code is: {0}","translationDE":"Der Code lautet: {0}","usedInClasses":["org.projectforge.web.My2FAHttpService"],"usedInFiles":["./projectforge-business/src/main/resources/mail/otpMail.html"]}, {"i18nKey":"user.My2FACode.sendCode.mail.sentSuccessfully","bundleName":"I18nResources","translation":"Mail was successfully sent to Your e-mail address at {0}","translationDE":"Der 2. Faktor wurde erfolgreich per E-Mail versendet um {0}","usedInClasses":["org.projectforge.web.My2FAHttpService"],"usedInFiles":[]}, {"i18nKey":"user.My2FACode.sendCode.mail.title","bundleName":"I18nResources","translation":"Message from ProjectForge","translationDE":"Nachricht von ProjectForge","usedInClasses":["org.projectforge.web.My2FAHttpService"],"usedInFiles":[]}, {"i18nKey":"user.My2FACode.sendCode.sms","bundleName":"I18nResources","translation":"Request SMS code","translationDE":"SMS-Code anfordern","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, - {"i18nKey":"user.My2FACode.sendCode.sms.info","bundleName":"I18nResources","translation":"Request code as text message to your mobile phone. The code will be valid up to 2 minutes.","translationDE":"Coder per SMS anfordern. Dieser Code ist bis zu 2 Minuten gültig.","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, + {"i18nKey":"user.My2FACode.sendCode.sms.info","bundleName":"I18nResources","translation":"Request code as text message to your mobile phone. The code will be valid up to 2 minutes.","translationDE":"Coder per SMS anfordern. Dieser Code ist bis zu 2 Minuten gültig.","usedInClasses":["org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, {"i18nKey":"user.My2FACode.sendCode.sms.sentSuccessfully","bundleName":"I18nResources","translation":"Message successfully sent to Your mobile phone at {0}.","translationDE":"Der 2. Faktor wurde erfolgreich per SMS versendet um {0}.","usedInClasses":["org.projectforge.web.My2FAHttpService"],"usedInFiles":[]}, {"i18nKey":"user.My2FACode.title","bundleName":"I18nResources","translation":"Two factor authentication","translationDE":"Zweifaktor-Authentifizierung","usedInClasses":["org.projectforge.rest.my2fa.My2FAPageRest","org.projectforge.rest.my2fa.My2FAServicesRest"],"usedInFiles":[]}, {"i18nKey":"user.activated","bundleName":"I18nResources","translation":"Activated","translationDE":"Aktiviert","usedInClasses":["org.projectforge.rest.UserPagesStatusFilter","org.projectforge.rest.UserPagesStatusFilter$STATUS","org.projectforge.web.user.UserEditForm","org.projectforge.web.user.UserListForm","org.projectforge.web.user.UserListPage"],"usedInFiles":[]}, @@ -2495,46 +2549,46 @@ {"i18nKey":"user.adminUsers","bundleName":"I18nResources","translation":"Admin users","translationDE":"Administrator:innen","usedInClasses":["org.projectforge.rest.UserPagesTypeFilter","org.projectforge.rest.UserPagesTypeFilter$TYPE","org.projectforge.web.user.UserListForm"],"usedInFiles":[]}, {"i18nKey":"user.adminUsers.none","bundleName":"I18nResources","translation":"Non-admin users","translationDE":"Nichtadministrator:innen","usedInClasses":["org.projectforge.web.user.UserListForm"],"usedInFiles":[]}, {"i18nKey":"user.assignedGroups","bundleName":"I18nResources","translation":"Assigned groups","translationDE":"Assoziierte Gruppen","usedInClasses":["org.projectforge.rest.GroupAccessPagesRest","org.projectforge.rest.MyAccountPageRest","org.projectforge.rest.UserPagesRest","org.projectforge.web.user.UserEditForm","org.projectforge.web.user.UserListPage"],"usedInFiles":[]}, - {"i18nKey":"user.authenticationToken.authenticator_key","bundleName":"I18nResources","translation":"2FA authenticator key","translationDE":"Schlüssel für Zweifaktor-Authentifizierung","usedInClasses":["org.projectforge.framework.persistence.user.entities.UserAuthenticationsDO"],"usedInFiles":[]}, - {"i18nKey":"user.authenticationToken.authenticator_key.tooltip","bundleName":"I18nResources","translation":"This authentication token is usable for Authenticator clients for Two-factor-authentication with e. g. Microsoft-, Google-authenticator app or Fortitoken.","translationDE":"Dieser Schlüssel kann für die Zweifaktor-Authentifizierung z. B. über die Apps Microsoft-, Google Authenticator oder Fortitoken benutzt werden.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"user.authenticationToken.authenticator_key","bundleName":"I18nResources","translation":"2FA authenticator key","translationDE":"Schlüssel für Zweifaktor-Authentifizierung","usedInClasses":["org.projectforge.framework.persistence.user.entities.UserAuthenticationsDO"],"usedInFiles":[]}, + {"i18nKey":"user.authenticationToken.authenticator_key.tooltip","bundleName":"I18nResources","translation":"This authentication token is usable for Authenticator clients for Two-factor-authentication with e. g. Microsoft-, Google-authenticator app or Fortitoken.","translationDE":"Dieser Schlüssel kann für die Zweifaktor-Authentifizierung z. B. über die Apps Microsoft-, Google Authenticator oder Fortitoken benutzt werden.","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"user.authenticationToken.button.showUsage","bundleName":"I18nResources","translation":"Usage","translationDE":"Info","usedInClasses":["org.projectforge.rest.MyAccountPageRest","org.projectforge.rest.TokenInfoPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.authenticationToken.button.showUsage.tooltip","bundleName":"I18nResources","translation":"Show usage of this token by clients.","translationDE":"Benutzung dieses Schüssels","usedInClasses":["org.projectforge.rest.MyAccountPageRest","org.projectforge.rest.TokenInfoPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.authenticationToken.calendar_rest","bundleName":"I18nResources","translation":"Authentication token for calendars","translationDE":"Schlüssel für Kalenderexport","usedInClasses":["org.projectforge.framework.persistence.user.entities.UserAuthenticationsDO"],"usedInFiles":[]}, - {"i18nKey":"user.authenticationToken.calendar_rest.tooltip","bundleName":"I18nResources","translation":"This authentication token is usable ical exports of calendars and the user's time sheets.","translationDE":"Dieser persönliche Schlüssel kann zum Export/Abonnement von Kalendern oder Zeitberichten verwendet werden.","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"user.authenticationToken.dav_token","bundleName":"I18nResources","translation":"Authentication token for CalDav/CardDAV","translationDE":"Schlüssel für CalDav/CardDAV","usedInClasses":["org.projectforge.framework.persistence.user.entities.UserAuthenticationsDO"],"usedInFiles":[]}, - {"i18nKey":"user.authenticationToken.dav_token.tooltip","bundleName":"I18nResources","translation":"This authentication token is usable the CalDAV/CardDAV functionality of ProjectForge.","translationDE":"Dieser persönliche Schlüssel kann zur Benutzung der CardDAV/CalDAV-Funktion verwendet werden.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"user.authenticationToken.button.showUsage.tooltip","bundleName":"I18nResources","translation":"Show usage of this token by clients.","translationDE":"Benutzung dieses Schüssels","usedInClasses":["org.projectforge.rest.MyAccountPageRest","org.projectforge.rest.TokenInfoPageRest"],"usedInFiles":[]}, + {"i18nKey":"user.authenticationToken.calendar_rest","bundleName":"I18nResources","translation":"Authentication token for calendars","translationDE":"Schlüssel für Kalenderexport","usedInClasses":["org.projectforge.framework.persistence.user.entities.UserAuthenticationsDO"],"usedInFiles":[]}, + {"i18nKey":"user.authenticationToken.calendar_rest.tooltip","bundleName":"I18nResources","translation":"This authentication token is usable ical exports of calendars and the user's time sheets.","translationDE":"Dieser persönliche Schlüssel kann zum Export/Abonnement von Kalendern oder Zeitberichten verwendet werden.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"user.authenticationToken.dav_token","bundleName":"I18nResources","translation":"Authentication token for CalDav/CardDAV","translationDE":"Schlüssel für CalDav/CardDAV","usedInClasses":["org.projectforge.framework.persistence.user.entities.UserAuthenticationsDO"],"usedInFiles":[]}, + {"i18nKey":"user.authenticationToken.dav_token.tooltip","bundleName":"I18nResources","translation":"This authentication token is usable the CalDAV/CardDAV functionality of ProjectForge.","translationDE":"Dieser persönliche Schlüssel kann zur Benutzung der CardDAV/CalDAV-Funktion verwendet werden.","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"user.authenticationToken.renew","bundleName":"I18nResources","translation":"Renew","translationDE":"Erneuern","usedInClasses":["org.projectforge.rest.MyAccountPageRest","org.projectforge.rest.UserPagesRest","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, - {"i18nKey":"user.authenticationToken.renew.securityQuestion","bundleName":"I18nResources","translation":"Do you really want to renew the authentication token (this is irevocable)?","translationDE":"Soll der Schlüssel wirklich unwiderrufbar erneuert werden?","usedInClasses":["org.projectforge.rest.MyAccountPageRest","org.projectforge.rest.UserPagesRest","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, - {"i18nKey":"user.authenticationToken.renew.successful","bundleName":"I18nResources","translation":"The authentication token is renewed and is now valid.","translationDE":"Der Schlüssel wurde erneuert und ist ab sofort gültig.","usedInClasses":["org.projectforge.rest.UserPagesRest","org.projectforge.rest.UserServicesRest","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, - {"i18nKey":"user.authenticationToken.renew.tooltip","bundleName":"I18nResources","translation":"You may renew this token if you are in doubt if your authentication is misused by any fraudster. If renewed any previous stored authentication key is invalid. Please note: This operation is irevocable.","translationDE":"Der Schlüssel kann erneuert werden, wenn der Verdacht auf einen möglichen Missbrauch vorliegt. Nach der Erneuerung sind vorher benutzte Schlüssel ungültig. Bitte beachten: Diese Operation ist unwiderruflich!","usedInClasses":["org.projectforge.rest.MyAccountPageRest","org.projectforge.rest.UserPagesRest","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, - {"i18nKey":"user.authenticationToken.rest_client","bundleName":"I18nResources","translation":"Authentication token for rest clients","translationDE":"Schlüssel für Rest-Clients","usedInClasses":["org.projectforge.framework.persistence.user.entities.UserAuthenticationsDO"],"usedInFiles":[]}, - {"i18nKey":"user.authenticationToken.rest_client.tooltip","bundleName":"I18nResources","translation":"This authentication token is usable for rest clients if ProjectForge's password shouldn't be used there.","translationDE":"Dieser persönliche Schlüssel kann zur Benutzung in Rest-Clients verwendet werden, wenn das ProjectForge-Passwort dort nicht verwendet werden soll.","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"user.authenticationToken.stay_logged_in_key","bundleName":"I18nResources","translation":"Key for stay-logged-in functionality","translationDE":"Schlüssel für Angemeldet-Bleiben-Funktion","usedInClasses":["org.projectforge.framework.persistence.user.entities.UserAuthenticationsDO"],"usedInFiles":[]}, - {"i18nKey":"user.authenticationToken.stay_logged_in_key.tooltip","bundleName":"I18nResources","translation":"This personal key is used for stay-logged-in functionality in your browser and will stored as cookie. After renewing all your browser sessions are invalid and re-login is required.","translationDE":"Dieser persönliche Schlüssel wird für die Angemeldet-Bleiben-Funktion im Browser verwendet und als Cookie im Browser gespeichert. Durch Erneuern können alle Browsersitzungen beendet werden.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"user.authenticationToken.renew.securityQuestion","bundleName":"I18nResources","translation":"Do you really want to renew the authentication token (this is irevocable)?","translationDE":"Soll der Schlüssel wirklich unwiderrufbar erneuert werden?","usedInClasses":["org.projectforge.rest.MyAccountPageRest","org.projectforge.rest.UserPagesRest","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, + {"i18nKey":"user.authenticationToken.renew.successful","bundleName":"I18nResources","translation":"The authentication token is renewed and is now valid.","translationDE":"Der Schlüssel wurde erneuert und ist ab sofort gültig.","usedInClasses":["org.projectforge.rest.UserPagesRest","org.projectforge.rest.UserServicesRest","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, + {"i18nKey":"user.authenticationToken.renew.tooltip","bundleName":"I18nResources","translation":"You may renew this token if you are in doubt if your authentication is misused by any fraudster. If renewed any previous stored authentication key is invalid. Please note: This operation is irevocable.","translationDE":"Der Schlüssel kann erneuert werden, wenn der Verdacht auf einen möglichen Missbrauch vorliegt. Nach der Erneuerung sind vorher benutzte Schlüssel ungültig. Bitte beachten: Diese Operation ist unwiderruflich!","usedInClasses":["org.projectforge.rest.MyAccountPageRest","org.projectforge.rest.UserPagesRest","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, + {"i18nKey":"user.authenticationToken.rest_client","bundleName":"I18nResources","translation":"Authentication token for rest clients","translationDE":"Schlüssel für Rest-Clients","usedInClasses":["org.projectforge.framework.persistence.user.entities.UserAuthenticationsDO"],"usedInFiles":[]}, + {"i18nKey":"user.authenticationToken.rest_client.tooltip","bundleName":"I18nResources","translation":"This authentication token is usable for rest clients if ProjectForge's password shouldn't be used there.","translationDE":"Dieser persönliche Schlüssel kann zur Benutzung in Rest-Clients verwendet werden, wenn das ProjectForge-Passwort dort nicht verwendet werden soll.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"user.authenticationToken.stay_logged_in_key","bundleName":"I18nResources","translation":"Key for stay-logged-in functionality","translationDE":"Schlüssel für Angemeldet-Bleiben-Funktion","usedInClasses":["org.projectforge.framework.persistence.user.entities.UserAuthenticationsDO"],"usedInFiles":[]}, + {"i18nKey":"user.authenticationToken.stay_logged_in_key.tooltip","bundleName":"I18nResources","translation":"This personal key is used for stay-logged-in functionality in your browser and will stored as cookie. After renewing all your browser sessions are invalid and re-login is required.","translationDE":"Dieser persönliche Schlüssel wird für die Angemeldet-Bleiben-Funktion im Browser verwendet und als Cookie im Browser gespeichert. Durch Erneuern können alle Browsersitzungen beendet werden.","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"user.changePassword.error.noCharacter","bundleName":"I18nResources","translation":"Password must have at minimum one letter.","translationDE":"Passwort muss mindestens einen Buchstaben haben.","usedInClasses":["org.projectforge.business.password.PasswordQualityServiceImpl"],"usedInFiles":[]}, {"i18nKey":"user.changePassword.error.noNonCharacter","bundleName":"I18nResources","translation":"Password must have at minimum one non-letter.","translationDE":"Passwort muss mindestens einen Nicht-Buchstaben haben.","usedInClasses":["org.projectforge.business.password.PasswordQualityServiceImpl"],"usedInFiles":[]}, {"i18nKey":"user.changePassword.error.notMinLength","bundleName":"I18nResources","translation":"Password must have at least {0} characters.","translationDE":"Passwort muss mindestens {0} Zeichen haben.","usedInClasses":["org.projectforge.business.password.PasswordQualityServiceImpl","org.projectforge.rest.AttachmentsServicesRest"],"usedInFiles":[]}, {"i18nKey":"user.changePassword.error.oldPasswdEqualsNew","bundleName":"I18nResources","translation":"New password should not be the same as the old one.","translationDE":"Neues Passwort darf nicht dem alten entsprechen.","usedInClasses":["org.projectforge.business.password.PasswordQualityServiceImpl"],"usedInFiles":[]}, {"i18nKey":"user.changePassword.error.oldPasswordWrong","bundleName":"I18nResources","translation":"Old password does not match.","translationDE":"Das alte Passwort wurde nicht korrekt eingegeben.","usedInClasses":["org.projectforge.business.user.service.UserService"],"usedInFiles":[]}, {"i18nKey":"user.changePassword.error.passwordQualityCheck","bundleName":"I18nResources","translation":"Password must have at least {0} characters and at minimum one letter and one non-letter character.","translationDE":"Passwort muss mindestens {0} Zeichen und sowohl mindestens einen Buchstaben als auch einen Nicht-Buchstaben enthalten.","usedInClasses":["org.projectforge.business.password.PasswordQualityServiceImpl"],"usedInFiles":[]}, - {"i18nKey":"user.changePassword.msg.passwordSuccessfullyChanged","bundleName":"I18nResources","translation":"Password successfully changed.","translationDE":"Passwort wurde erfolgreich geändert.","usedInClasses":["org.projectforge.rest.ChangePasswordPageRest","org.projectforge.rest.pub.PasswordResetPageRest"],"usedInFiles":[]}, + {"i18nKey":"user.changePassword.msg.passwordSuccessfullyChanged","bundleName":"I18nResources","translation":"Password successfully changed.","translationDE":"Passwort wurde erfolgreich geändert.","usedInClasses":["org.projectforge.rest.ChangePasswordPageRest","org.projectforge.rest.pub.PasswordResetPageRest"],"usedInFiles":[]}, {"i18nKey":"user.changePassword.newPassword","bundleName":"I18nResources","translation":"New password","translationDE":"Neues Passwort","usedInClasses":["org.projectforge.rest.pub.PasswordResetPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.changePassword.notSupported","bundleName":"I18nResources","translation":"The change of password isn't supported. Please contact your administrator.","translationDE":"Die Passwortänderung wird nicht unterstützt. Kontaktieren Sie Ihre:n Administrator:in.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"user.changePassword.notSupported","bundleName":"I18nResources","translation":"The change of password isn't supported. Please contact your administrator.","translationDE":"Die Passwortänderung wird nicht unterstützt. Kontaktieren Sie Ihre:n Administrator:in.","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"user.changePassword.oldPassword","bundleName":"I18nResources","translation":"Old password","translationDE":"Altes Passwort","usedInClasses":["org.projectforge.rest.ChangePasswordPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.changePassword.password.lastChange","bundleName":"I18nResources","translation":"Last change of password","translationDE":"Letzte Passwortänderung","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.rest.UserPagesRest"],"usedInFiles":[]}, - {"i18nKey":"user.changePassword.title","bundleName":"I18nResources","translation":"Change own password","translationDE":"Eigenes Passwort ändern","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"user.changePassword.password.lastChange","bundleName":"I18nResources","translation":"Last change of password","translationDE":"Letzte Passwortänderung","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.rest.UserPagesRest"],"usedInFiles":[]}, + {"i18nKey":"user.changePassword.title","bundleName":"I18nResources","translation":"Change own password","translationDE":"Eigenes Passwort ändern","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"user.changeWlanPassword.error.loginPasswordWrong","bundleName":"I18nResources","translation":"The login password is not correct.","translationDE":"Das Login-Passwort wurde nicht korrekt eingegeben.","usedInClasses":["org.projectforge.business.user.service.UserService"],"usedInFiles":[]}, - {"i18nKey":"user.changeWlanPassword.lastChange","bundleName":"I18nResources","translation":"Last change of WLAN/Samba password","translationDE":"Letzte WLAN/Samba-Passwortänderung","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.rest.UserPagesRest"],"usedInFiles":[]}, + {"i18nKey":"user.changeWlanPassword.lastChange","bundleName":"I18nResources","translation":"Last change of WLAN/Samba password","translationDE":"Letzte WLAN/Samba-Passwortänderung","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.rest.UserPagesRest"],"usedInFiles":[]}, {"i18nKey":"user.changeWlanPassword.loginPassword","bundleName":"I18nResources","translation":"Login password","translationDE":"Login-Passwort","usedInClasses":["org.projectforge.rest.ChangeWlanPasswordPageRest"],"usedInFiles":[]}, {"i18nKey":"user.changeWlanPassword.newPassword","bundleName":"I18nResources","translation":"New WLAN password","translationDE":"Neues WLAN-Passwort","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"user.changeWlanPassword.title","bundleName":"I18nResources","translation":"Change WLAN/Samba password","translationDE":"WLAN/Samba Passwort ändern","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"user.changeWlanPassword.title","bundleName":"I18nResources","translation":"Change WLAN/Samba password","translationDE":"WLAN/Samba Passwort ändern","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"user.deactivated","bundleName":"I18nResources","translation":"deactivated","translationDE":"Deaktiviert","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.rest.UserPagesRest","org.projectforge.rest.UserPagesStatusFilter","org.projectforge.rest.UserPagesStatusFilter$STATUS","org.projectforge.web.user.UserListForm"],"usedInFiles":[]}, {"i18nKey":"user.defaultLocale","bundleName":"I18nResources","translation":"Default (browser)","translationDE":"Default (Browser)","usedInClasses":["org.projectforge.rest.UserPagesRest","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, - {"i18nKey":"user.error.passwordAndRepeatDoesNotMatch","bundleName":"I18nResources","translation":"Password and password repeat does not match.","translationDE":"Passwort und Passwortwiederholung stimmen nicht überein.","usedInClasses":["org.projectforge.rest.ChangePasswordPageRest","org.projectforge.rest.pub.PasswordResetPageRest","org.projectforge.web.admin.SetupForm","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, + {"i18nKey":"user.error.passwordAndRepeatDoesNotMatch","bundleName":"I18nResources","translation":"Password and password repeat does not match.","translationDE":"Passwort und Passwortwiederholung stimmen nicht überein.","usedInClasses":["org.projectforge.rest.ChangePasswordPageRest","org.projectforge.rest.pub.PasswordResetPageRest","org.projectforge.web.admin.SetupForm","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, {"i18nKey":"user.error.usernameAlreadyExists","bundleName":"I18nResources","translation":"User name already exists.","translationDE":"Benutzer:inname ist bereits vergeben.","usedInClasses":["org.projectforge.rest.UserPagesRest","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, {"i18nKey":"user.filter.hrPlanning","bundleName":"I18nResources","translation":"HR planning","translationDE":"Personalplanung","usedInClasses":["org.projectforge.rest.UserPagesRest"],"usedInFiles":[]}, {"i18nKey":"user.filter.privileged","bundleName":"I18nResources","translation":"Privileged users","translationDE":"Privilegierte Benutzer:innen","usedInClasses":["org.projectforge.rest.UserPagesTypeFilter","org.projectforge.rest.UserPagesTypeFilter$TYPE"],"usedInFiles":[]}, - {"i18nKey":"user.filter.restricted","bundleName":"I18nResources","translation":"Restricted users","translationDE":"Eingeschränkte Benutzer:innen","usedInClasses":["org.projectforge.rest.UserPagesTypeFilter","org.projectforge.rest.UserPagesTypeFilter$TYPE"],"usedInFiles":[]}, + {"i18nKey":"user.filter.restricted","bundleName":"I18nResources","translation":"Restricted users","translationDE":"Eingeschränkte Benutzer:innen","usedInClasses":["org.projectforge.rest.UserPagesTypeFilter","org.projectforge.rest.UserPagesTypeFilter$TYPE"],"usedInFiles":[]}, {"i18nKey":"user.filter.status","bundleName":"I18nResources","translation":"State","translationDE":"Status","usedInClasses":["org.projectforge.rest.UserPagesRest"],"usedInFiles":[]}, {"i18nKey":"user.filter.syncStatus","bundleName":"I18nResources","translation":"Sync state","translationDE":"Synchronisationsstatus","usedInClasses":["org.projectforge.rest.UserPagesRest"],"usedInFiles":[]}, {"i18nKey":"user.filter.type","bundleName":"I18nResources","translation":"Type","translationDE":"Typ","usedInClasses":["org.projectforge.rest.UserPagesRest"],"usedInFiles":[]}, @@ -2543,67 +2597,67 @@ {"i18nKey":"user.hrPlanningEnabled.not","bundleName":"I18nResources","translation":"No HR planning","translationDE":"keine Personalplanung","usedInClasses":["org.projectforge.rest.UserPagesHRPlanningFilter","org.projectforge.rest.UserPagesHRPlanningFilter$PLANNING_TYPE","org.projectforge.web.user.UserListForm"],"usedInFiles":[]}, {"i18nKey":"user.hrPlanningEnabled.tooltip","bundleName":"I18nResources","translation":"Should the user be available in HR planning?","translationDE":"Soll der oder die Benutzer:in in der Personalplanung sichtbar sein?","usedInClasses":["org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, {"i18nKey":"user.jiraUsername","bundleName":"I18nResources","translation":"JIRA user name","translationDE":"JIRA Benutzer:inname","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, - {"i18nKey":"user.jiraUsername.tooltip","bundleName":"I18nResources","translation":"Only needed, if the ProjectForge user name differs from the JIRA user name and the user want to work with JIRA inside ProjectForge.","translationDE":"Wird nur gebraucht, wenn dieser von dem ProjectForge-Benutzer:innamen abweicht und der oder die Benutzer:in aus ProjectForge heraus mit JIRA arbeiten möchte.","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, + {"i18nKey":"user.jiraUsername.tooltip","bundleName":"I18nResources","translation":"Only needed, if the ProjectForge user name differs from the JIRA user name and the user want to work with JIRA inside ProjectForge.","translationDE":"Wird nur gebraucht, wenn dieser von dem ProjectForge-Benutzer:innamen abweicht und der oder die Benutzer:in aus ProjectForge heraus mit JIRA arbeiten möchte.","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, {"i18nKey":"user.ldapValues","bundleName":"I18nResources","translation":"LDAP","translationDE":"LDAP","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserListPage"],"usedInFiles":[]}, - {"i18nKey":"user.list.select.title","bundleName":"I18nResources","translation":"Select user","translationDE":"Benutzer:in auswählen","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"user.list.select.title","bundleName":"I18nResources","translation":"Select user","translationDE":"Benutzer:in auswählen","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"user.localUser","bundleName":"I18nResources","translation":"Local user","translationDE":"Lokale:r Benutzer:in","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.rest.UserPagesSyncFilter","org.projectforge.rest.UserPagesSyncFilter$Sync","org.projectforge.web.user.UserEditForm","org.projectforge.web.user.UserListForm"],"usedInFiles":[]}, {"i18nKey":"user.localUser.not","bundleName":"I18nResources","translation":"Common user","translationDE":"Allgemeine:r Benutzer:in","usedInClasses":["org.projectforge.rest.UserPagesSyncFilter","org.projectforge.rest.UserPagesSyncFilter$Sync","org.projectforge.web.user.UserListForm"],"usedInFiles":[]}, {"i18nKey":"user.localUser.tooltip","bundleName":"I18nResources","translation":"Local users will not be synchronized with an external user management system (such as LDAP).","translationDE":"Lokale Benutzer:innen werden nicht mit einem externen Benutzer:innensystem synchronisiert (z. B. LDAP).","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, {"i18nKey":"user.locale","bundleName":"I18nResources","translation":"Language","translationDE":"Sprache","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, {"i18nKey":"user.menu.editRights","bundleName":"I18nResources","translation":"User rights","translationDE":"Benutzer:innenrechte","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"user.mobilePhone","bundleName":"I18nResources","translation":"Mobile phone","translationDE":"Mobilnummer","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, - {"i18nKey":"user.mobilePhone.info","bundleName":"I18nResources","translation":"Mobile phone usable as a 2nd factor (required e. g. for password reset or other services). You may configure this field by any admin or by configuring your two-factor-authentication.","translationDE":"Die Nummer kann als 2. Faktor benutzt werden, um z. B. das Passwort zurückzusetzen oder andere sicherheitsrelevante Bereiche zu nutzen. Die Nummer kann im Bereich Zwei-Faktor-Authentifizierung geändert werden.","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, - {"i18nKey":"user.mobilePhone.invalidFormat","bundleName":"I18nResources","translation":"Valid characters for phone numbers are '+' as first char, '-', '/' and spaces. The leading country code is optional, e. g.: +49 561 316793-0 or 0561 316793-0","translationDE":"Es sind nur Zahlen sowie '+' am Anfang, '-', '/' und Leerzeichen erlaubt. Die führende Ländervorwahl ist optional. Beispiel: +49 561 316793-0 oder 0561 316793-0","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, - {"i18nKey":"user.myAccount.teamcalwhitelist","bundleName":"I18nResources","translation":"Calendar whitelist for calendar software","translationDE":"Kalender Whitelist für Kalendersoftware","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"user.mobilePhone.info","bundleName":"I18nResources","translation":"Mobile phone usable as a 2nd factor (required e. g. for password reset or other services). You may configure this field by any admin or by configuring your two-factor-authentication.","translationDE":"Die Nummer kann als 2. Faktor benutzt werden, um z. B. das Passwort zurückzusetzen oder andere sicherheitsrelevante Bereiche zu nutzen. Die Nummer kann im Bereich Zwei-Faktor-Authentifizierung geändert werden.","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, + {"i18nKey":"user.mobilePhone.invalidFormat","bundleName":"I18nResources","translation":"Valid characters for phone numbers are '+' as first char, '-', '/' and spaces. The leading country code is optional, e. g.: +49 561 316793-0 or 0561 316793-0","translationDE":"Es sind nur Zahlen sowie '+' am Anfang, '-', '/' und Leerzeichen erlaubt. Die führende Ländervorwahl ist optional. Beispiel: +49 561 316793-0 oder 0561 316793-0","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, + {"i18nKey":"user.myAccount.teamcalwhitelist","bundleName":"I18nResources","translation":"Calendar whitelist for calendar software","translationDE":"Kalender Whitelist für Kalendersoftware","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"user.myAccount.title.edit","bundleName":"I18nResources","translation":"Edit my account","translationDE":"Mein Zugang","usedInClasses":["org.projectforge.rest.MyAccountPageRest"],"usedInFiles":[]}, {"i18nKey":"user.panel.error.usernameNotFound","bundleName":"I18nResources","translation":"User name doesn't exist.","translationDE":"Benutzer:inname nicht existent.","usedInClasses":["org.projectforge.web.user.UserSelectPanel"],"usedInFiles":[]}, {"i18nKey":"user.personalPhoneIdentifiers","bundleName":"I18nResources","translation":"Phone ids","translationDE":"Telefonkennungen","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm","org.projectforge.web.user.UserListPage"],"usedInFiles":[]}, - {"i18nKey":"user.personalPhoneIdentifiers.pleaseDefine","bundleName":"I18nResources","translation":"Define phone numbers under 'My account'.","translationDE":"Anschlüsse festlegen unter 'Mein Zugang'","usedInClasses":["org.projectforge.web.address.PhoneCallForm"],"usedInFiles":[]}, - {"i18nKey":"user.personalPhoneIdentifiers.tooltip.content","bundleName":"I18nResources","translation":"Define here your personal phone id's in your company (id = internal phone number). Multiple values should be comma separated. In address list you can choose your current phone for direct dialing by clicking on a phone number in the address list.","translationDE":"Hier können benutzerspezifische Telefon-Ids (= interne Rufnummer) als kommaseparierte Werte angegeben werden. In der Adressenliste kann somit eine Direktwahl von diesen Telefonen durch Klicken auf die gewünschte Rufnummer initiiert werden.","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, + {"i18nKey":"user.personalPhoneIdentifiers.pleaseDefine","bundleName":"I18nResources","translation":"Define phone numbers under 'My account'.","translationDE":"Anschlüsse festlegen unter 'Mein Zugang'","usedInClasses":["org.projectforge.web.address.PhoneCallForm"],"usedInFiles":[]}, + {"i18nKey":"user.personalPhoneIdentifiers.tooltip.content","bundleName":"I18nResources","translation":"Define here your personal phone id's in your company (id = internal phone number). Multiple values should be comma separated. In address list you can choose your current phone for direct dialing by clicking on a phone number in the address list.","translationDE":"Hier können benutzerspezifische Telefon-Ids (= interne Rufnummer) als kommaseparierte Werte angegeben werden. In der Adressenliste kann somit eine Direktwahl von diesen Telefonen durch Klicken auf die gewünschte Rufnummer initiiert werden.","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, {"i18nKey":"user.personalPhoneIdentifiers.tooltip.title","bundleName":"I18nResources","translation":"Direct call","translationDE":"Direktwahl","usedInClasses":["org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, - {"i18nKey":"user.restricted","bundleName":"I18nResources","translation":"restricted","translationDE":"eingeschränkt","usedInClasses":["org.projectforge.web.user.UserListForm"],"usedInFiles":[]}, - {"i18nKey":"user.restricted.not","bundleName":"I18nResources","translation":"not restricted","translationDE":"nicht eingeschränkt","usedInClasses":["org.projectforge.web.user.UserListForm"],"usedInFiles":[]}, - {"i18nKey":"user.restrictedUser","bundleName":"I18nResources","translation":"Restricted user","translationDE":"Eingeschränkte:r Nutzer:in","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm","org.projectforge.web.user.UserListForm"],"usedInFiles":[]}, - {"i18nKey":"user.restrictedUser.tooltip","bundleName":"I18nResources","translation":"A restricted user has only the ability to log-in and to change his password, but the user is synchronized with the external user management system.","translationDE":"Ein eingeschränkte:r (restricted) Nutzer:in darf sich lediglich anmelden und sein Passwort ändern. Der oder die Nutzer:in wird mit einem externen Usermanagementsystem synchronisiert.","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, + {"i18nKey":"user.restricted","bundleName":"I18nResources","translation":"restricted","translationDE":"eingeschränkt","usedInClasses":["org.projectforge.web.user.UserListForm"],"usedInFiles":[]}, + {"i18nKey":"user.restricted.not","bundleName":"I18nResources","translation":"not restricted","translationDE":"nicht eingeschränkt","usedInClasses":["org.projectforge.web.user.UserListForm"],"usedInFiles":[]}, + {"i18nKey":"user.restrictedUser","bundleName":"I18nResources","translation":"Restricted user","translationDE":"Eingeschränkte:r Nutzer:in","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm","org.projectforge.web.user.UserListForm"],"usedInFiles":[]}, + {"i18nKey":"user.restrictedUser.tooltip","bundleName":"I18nResources","translation":"A restricted user has only the ability to log-in and to change his password, but the user is synchronized with the external user management system.","translationDE":"Ein eingeschränkte:r (restricted) Nutzer:in darf sich lediglich anmelden und sein Passwort ändern. Der oder die Nutzer:in wird mit einem externen Usermanagementsystem synchronisiert.","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm"],"usedInFiles":[]}, {"i18nKey":"user.rights.title.edit","bundleName":"I18nResources","translation":"Edit user rights","translationDE":"Benutzer:innenrechte bearbeiten","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"user.sshPublicKey","bundleName":"I18nResources","translation":"SSH public key","translationDE":"SSH public key","usedInClasses":["org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.web.user.UserEditForm","org.projectforge.web.user.UserListPage"],"usedInFiles":[]}, {"i18nKey":"user.title.add","bundleName":"I18nResources","translation":"Add new user","translationDE":"Neue:r Benutzer:in","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"user.title.edit","bundleName":"I18nResources","translation":"Edit user","translationDE":"Benutzer:in bearbeiten","usedInClasses":["org.projectforge.web.user.UserEditPage","org.projectforge.web.user.UserListPage"],"usedInFiles":[]}, {"i18nKey":"user.title.heading","bundleName":"I18nResources","translation":"Users","translationDE":"Benutzer:in","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"user.title.list","bundleName":"I18nResources","translation":"List of users","translationDE":"Benutzer:in","usedInClasses":["org.projectforge.web.user.UserEditPage","org.projectforge.web.user.UserListPage"],"usedInFiles":[]}, - {"i18nKey":"user.title.list.select","bundleName":"I18nResources","translation":"Select user","translationDE":"Benutzer:in auswählen","usedInClasses":["org.projectforge.web.user.UserEditPage","org.projectforge.web.user.UserListPage"],"usedInFiles":[]}, + {"i18nKey":"user.title.list.select","bundleName":"I18nResources","translation":"Select user","translationDE":"Benutzer:in auswählen","usedInClasses":["org.projectforge.web.user.UserEditPage","org.projectforge.web.user.UserListPage"],"usedInFiles":[]}, {"i18nKey":"user.unassignedGroups","bundleName":"I18nResources","translation":"Unassigned groups","translationDE":"Nicht assoziierte Gruppen","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"user.username","bundleName":"I18nResources","translation":"User name","translationDE":"Benutzer:inname","usedInClasses":["org.projectforge.business.fibu.EmployeeDao","org.projectforge.business.humanresources.HRPlanningDao","org.projectforge.business.timesheet.TimesheetDao","org.projectforge.business.user.UserPrefDao","org.projectforge.business.user.UserRightDao","org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.rest.ChangePasswordPageRest","org.projectforge.rest.calendar.VacationServicesRest","org.projectforge.rest.fibu.EmployeePagesRest","org.projectforge.web.fibu.EmployeeSelectPanel","org.projectforge.web.user.UserListPage"],"usedInFiles":[]}, {"i18nKey":"user.users","bundleName":"I18nResources","translation":"Users","translationDE":"Benutzer:in","usedInClasses":["org.projectforge.web.user.UserListPage"],"usedInFiles":[]}, {"i18nKey":"userPref.area","bundleName":"I18nResources","translation":"Area","translationDE":"Bereich","usedInClasses":["org.projectforge.web.user.UserPrefEditForm","org.projectforge.web.user.UserPrefListPage"],"usedInFiles":[]}, {"i18nKey":"userPref.area.jira.project","bundleName":"I18nResources","translation":"JIRA project","translationDE":"JIRA Projekt","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"userPref.area.jira.project.pid","bundleName":"I18nResources","translation":"Project id (pid)","translationDE":"Projekt ID (PID)","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"userPref.area.jira.project.pid.tooltip","bundleName":"I18nResources","translation":"You can get the project pid from JIRA dashboard from some links (url parameter called projectID or pid). It's needed for the communication with JIRA.","translationDE":"Kann im JIRA-Dashboard z. B. aus einigen Links abgelesen werden (projectID oder pid in der referenzierten URL). Wird für die Kommunikation mit JIRA benötigt.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"userPref.area.jira.project.pid.tooltip","bundleName":"I18nResources","translation":"You can get the project pid from JIRA dashboard from some links (url parameter called projectID or pid). It's needed for the communication with JIRA.","translationDE":"Kann im JIRA-Dashboard z. B. aus einigen Links abgelesen werden (projectID oder pid in der referenzierten URL). Wird für die Kommunikation mit JIRA benötigt.","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"userPref.area.kunde.favorite","bundleName":"I18nResources","translation":"Customer favorite","translationDE":"Kundenfavorit","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"userPref.area.projekt.favorite","bundleName":"I18nResources","translation":"Project favorite","translationDE":"Projektfavorit","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"userPref.area.task.favorite","bundleName":"I18nResources","translation":"Structure element favorite","translationDE":"Strukturelementfavorit","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"userPref.area.timesheet.template","bundleName":"I18nResources","translation":"Templates for time sheets","translationDE":"Zeitberichtsvorlage","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"userPref.area.user.favorite","bundleName":"I18nResources","translation":"User favorite (unused)","translationDE":"Benutzerfavorit (unbenutzt)","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"userPref.error.nameDoesAlreadyExist","bundleName":"I18nResources","translation":"An entry with this name does already exist.","translationDE":"Es existiert bereits ein Eintrag unter diesem Namen.","usedInClasses":["org.projectforge.web.user.UserPrefEditForm"],"usedInFiles":[]}, - {"i18nKey":"userPref.error.userIsNotOwner","bundleName":"I18nResources","translation":"User is not owner of preference entry.","translationDE":"Benutzer:in ist nicht Eigner:in dieser persönlichen Einstellung.","usedInClasses":["org.projectforge.business.user.UserPrefDao"],"usedInFiles":[]}, + {"i18nKey":"userPref.error.userIsNotOwner","bundleName":"I18nResources","translation":"User is not owner of preference entry.","translationDE":"Benutzer:in ist nicht Eigner:in dieser persönlichen Einstellung.","usedInClasses":["org.projectforge.business.user.UserPrefDao"],"usedInFiles":[]}, {"i18nKey":"userPref.favorite.create","bundleName":"I18nResources","translation":"--- create ---","translationDE":"--- anlegen ---","usedInClasses":["org.projectforge.web.wicket.components.FavoritesChoicePanel"],"usedInFiles":[]}, {"i18nKey":"userPref.favorite.select","bundleName":"I18nResources","translation":"--- Favorites ---","translationDE":"--- Favoriten ---","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"userPref.name","bundleName":"I18nResources","translation":"Name","translationDE":"Name","usedInClasses":["org.projectforge.web.user.UserPrefEditForm","org.projectforge.web.user.UserPrefListPage"],"usedInFiles":[]}, {"i18nKey":"userPref.saveAsTemplate","bundleName":"I18nResources","translation":"Save as template","translationDE":"Als Vorlage speichern","usedInClasses":["org.projectforge.plugins.todo.ToDoEditForm","org.projectforge.web.timesheet.TimesheetEditForm"],"usedInFiles":[]}, {"i18nKey":"userPref.template.create","bundleName":"I18nResources","translation":"--- create ---","translationDE":"--- anlegen ---","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"userPref.template.select","bundleName":"I18nResources","translation":"--- Templates ---","translationDE":"--- Vorlagen ---","usedInClasses":["org.projectforge.plugins.todo.ToDoEditForm","org.projectforge.web.timesheet.TimesheetEditForm"],"usedInFiles":[]}, - {"i18nKey":"userPref.title.add","bundleName":"I18nResources","translation":"New user preference","translationDE":"Neue persönliche Einstellung","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"userPref.title.edit","bundleName":"I18nResources","translation":"Edit user preference","translationDE":"Persönliche Einstellung bearbeiten","usedInClasses":["org.projectforge.web.user.UserPrefEditPage","org.projectforge.web.user.UserPrefListPage"],"usedInFiles":[]}, - {"i18nKey":"userPref.title.heading","bundleName":"I18nResources","translation":"User preferences","translationDE":"Persönliche Einstellungen","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"userPref.title.list","bundleName":"I18nResources","translation":"List of user preferences","translationDE":"Liste der persönlichen Einstellungen","usedInClasses":["org.projectforge.web.user.UserPrefEditPage","org.projectforge.web.user.UserPrefListPage"],"usedInFiles":[]}, + {"i18nKey":"userPref.title.add","bundleName":"I18nResources","translation":"New user preference","translationDE":"Neue persönliche Einstellung","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"userPref.title.edit","bundleName":"I18nResources","translation":"Edit user preference","translationDE":"Persönliche Einstellung bearbeiten","usedInClasses":["org.projectforge.web.user.UserPrefEditPage","org.projectforge.web.user.UserPrefListPage"],"usedInFiles":[]}, + {"i18nKey":"userPref.title.heading","bundleName":"I18nResources","translation":"User preferences","translationDE":"Persönliche Einstellungen","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"userPref.title.list","bundleName":"I18nResources","translation":"List of user preferences","translationDE":"Liste der persönlichen Einstellungen","usedInClasses":["org.projectforge.web.user.UserPrefEditPage","org.projectforge.web.user.UserPrefListPage"],"usedInFiles":[]}, {"i18nKey":"username","bundleName":"I18nResources","translation":"User name","translationDE":"Benutzer:innenname","usedInClasses":["org.projectforge.business.book.BookDO","org.projectforge.business.fibu.EmployeeDO","org.projectforge.business.ldap.PFUserDOConverter","org.projectforge.business.user.UserAuthenticationsDao","org.projectforge.business.user.UserDao","org.projectforge.framework.ToStringUtil","org.projectforge.framework.persistence.database.HistoryMigrateService","org.projectforge.framework.persistence.user.entities.PFUserDO","org.projectforge.plugins.merlin.rest.MerlinExecutionPageRest","org.projectforge.rest.MenuRest","org.projectforge.rest.MyAccountPageRest","org.projectforge.rest.UserPagesRest","org.projectforge.rest.calendar.CalendarSubscriptionInfoPageRest","org.projectforge.rest.pub.LoginPageRest","org.projectforge.rest.pub.PasswordResetPageRest","org.projectforge.rest.pub.SetupPageRest","org.projectforge.ui.UIInput","org.projectforge.web.admin.SetupForm","org.projectforge.web.user.UserEditForm","org.projectforge.web.user.UserListPage","org.projectforge.web.user.UserSelectPanel"],"usedInFiles":[]}, {"i18nKey":"vacation","bundleName":"I18nResources","translation":"Vacation","translationDE":"Urlaub","usedInClasses":["org.projectforge.business.vacation.service.VacationExcelExporter","org.projectforge.business.vacation.service.VacationSendMailService","org.projectforge.menu.builder.MenuItemDefId","org.projectforge.rest.TeamCalPagesRest","org.projectforge.rest.VacationAccountPageRest","org.projectforge.rest.VacationExportPageRest","org.projectforge.rest.VacationPagesRest","org.projectforge.rest.calendar.FullCalendarEvent","org.projectforge.rest.calendar.VacationProvider","org.projectforge.rest.pub.CalendarSubscriptionServiceRest"],"usedInFiles":[]}, {"i18nKey":"vacation.Days","bundleName":"I18nResources","translation":"Vacation days","translationDE":"Urlaubstage","usedInClasses":["org.projectforge.rest.VacationAccountPageRest","org.projectforge.rest.VacationPagesRest"],"usedInFiles":[]}, - {"i18nKey":"vacation.annualleave","bundleName":"I18nResources","translation":"Number of leave days for the year","translationDE":"Anzahl Urlaubstage für Kalenderjahr","usedInClasses":["org.projectforge.rest.VacationAccountPageRest","org.projectforge.web.fibu.MonthlyEmployeeReportPage"],"usedInFiles":[]}, - {"i18nKey":"vacation.availabledays","bundleName":"I18nResources","translation":"Available vacation days","translationDE":"Verfügbare Urlaubstage","usedInClasses":["org.projectforge.rest.VacationPagesRest"],"usedInFiles":[]}, - {"i18nKey":"vacation.availablevacation","bundleName":"I18nResources","translation":"Available vacation","translationDE":"Noch verfügbarer Urlaub","usedInClasses":["org.projectforge.rest.VacationAccountPageRest"],"usedInFiles":[]}, + {"i18nKey":"vacation.annualleave","bundleName":"I18nResources","translation":"Number of leave days for the year","translationDE":"Anzahl Urlaubstage für Kalenderjahr","usedInClasses":["org.projectforge.rest.VacationAccountPageRest","org.projectforge.web.fibu.MonthlyEmployeeReportPage"],"usedInFiles":[]}, + {"i18nKey":"vacation.availabledays","bundleName":"I18nResources","translation":"Available vacation days","translationDE":"Verfügbare Urlaubstage","usedInClasses":["org.projectforge.rest.VacationPagesRest"],"usedInFiles":[]}, + {"i18nKey":"vacation.availablevacation","bundleName":"I18nResources","translation":"Available vacation","translationDE":"Noch verfügbarer Urlaub","usedInClasses":["org.projectforge.rest.VacationAccountPageRest"],"usedInFiles":[]}, {"i18nKey":"vacation.calendar","bundleName":"I18nResources","translation":"Calendar","translationDE":"Kalender","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"vacation.conflict.info","bundleName":"I18nResources","translation":"A conflict exists: At least at one day no substitute is available. Please choose other or further substitutes.","translationDE":"Es besteht ein Konflikt mit den Abwesenheiten der Vertreter:innen. An mindestens einem Tag steht kein:e Vertreter:in zur Verfügung. Bitte andere oder weitere Vertretungen auswählen.","usedInClasses":["org.projectforge.rest.VacationPagesRest"],"usedInFiles":[]}, + {"i18nKey":"vacation.conflict.info","bundleName":"I18nResources","translation":"A conflict exists: At least at one day no substitute is available. Please choose other or further substitutes.","translationDE":"Es besteht ein Konflikt mit den Abwesenheiten der Vertreter:innen. An mindestens einem Tag steht kein:e Vertreter:in zur Verfügung. Bitte andere oder weitere Vertretungen auswählen.","usedInClasses":["org.projectforge.rest.VacationPagesRest"],"usedInFiles":[]}, {"i18nKey":"vacation.conflicts","bundleName":"I18nResources","translation":"Conflicts","translationDE":"Konflikte","usedInClasses":["org.projectforge.rest.VacationPagesRest"],"usedInFiles":[]}, {"i18nKey":"vacation.countPerDay","bundleName":"I18nResources","translation":"vacation hours per day","translationDE":"Urlaubsstunden pro Urlaubstag","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"vacation.employee","bundleName":"I18nResources","translation":"Employee","translationDE":"Mitarbeiter","usedInClasses":["org.projectforge.business.vacation.model.RemainingLeaveDO","org.projectforge.business.vacation.model.VacationDO","org.projectforge.rest.VacationPagesRest"],"usedInFiles":[]}, @@ -2626,21 +2680,21 @@ {"i18nKey":"vacation.mail.action","bundleName":"I18nResources","translation":"The application for leave of {0} for the period {1} was {2} by {3}.","translationDE":"Der Urlaubseintrag von {0} am {1} wurde {2} durch {3}.","usedInClasses":[],"usedInFiles":["./projectforge-business/src/main/resources/mail/vacationMail.html"]}, {"i18nKey":"vacation.mail.action.short","bundleName":"I18nResources","translation":"Application for leave of {0} for {1} was {2}","translationDE":"Urlaubseintrag von {0} am {1} wurde {2}","usedInClasses":["org.projectforge.business.vacation.service.VacationSendMailService"],"usedInFiles":[]}, {"i18nKey":"vacation.mail.link","bundleName":"I18nResources","translation":"See application","translationDE":"Zum Urlaubseintrag","usedInClasses":[],"usedInFiles":["./projectforge-business/src/main/resources/mail/vacationMail.html"]}, - {"i18nKey":"vacation.mail.modType.delete","bundleName":"I18nResources","translation":"deleted","translationDE":"gelöscht","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"vacation.mail.modType.delete","bundleName":"I18nResources","translation":"deleted","translationDE":"gelöscht","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"vacation.mail.modType.insert","bundleName":"I18nResources","translation":"created","translationDE":"angelegt","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"vacation.mail.modType.undelete","bundleName":"I18nResources","translation":"undeleted","translationDE":"wiederhergestellt","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"vacation.mail.modType.update","bundleName":"I18nResources","translation":"modified","translationDE":"geändert","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"vacation.mail.modType.update","bundleName":"I18nResources","translation":"modified","translationDE":"geändert","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"vacation.mail.period","bundleName":"I18nResources","translation":"{0}-{1} ({2} days)","translationDE":"{0}-{1} ({2} Tage)","usedInClasses":["org.projectforge.business.vacation.service.VacationSendMailService"],"usedInFiles":[]}, - {"i18nKey":"vacation.mail.reason.hr","bundleName":"I18nResources","translation":"You received this application for leave of {0}, because it''s a special vacation which needs approvement by any hr staff member.","translationDE":"Du erhählst diesen Urlaubseintrag von {0}, weil es ein Spezialurlaub ist, der die Freigabe von HR benötigt.","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"vacation.mail.reason.manager","bundleName":"I18nResources","translation":"You were chosen for checking this application. Please edit this application for leave in ProjectForge or contact {0}.","translationDE":"Du wurdest zur Abstimmung ausgewählt. Bitte bearbeite diesen Urlaubseintrag in ProjectForge bzw. nimm bitte Kontakt zu {0} auf.","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"vacation.mail.reason.other","bundleName":"I18nResources","translation":"You received this application for leave of {0} for information.","translationDE":"Du erhählst diesen Urlaubseintrag von {0} zur Information.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"vacation.mail.reason.hr","bundleName":"I18nResources","translation":"You received this application for leave of {0}, because it''s a special vacation which needs approvement by any hr staff member.","translationDE":"Du erhählst diesen Urlaubseintrag von {0}, weil es ein Spezialurlaub ist, der die Freigabe von HR benötigt.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"vacation.mail.reason.manager","bundleName":"I18nResources","translation":"You were chosen for checking this application. Please edit this application for leave in ProjectForge or contact {0}.","translationDE":"Du wurdest zur Abstimmung ausgewählt. Bitte bearbeite diesen Urlaubseintrag in ProjectForge bzw. nimm bitte Kontakt zu {0} auf.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"vacation.mail.reason.other","bundleName":"I18nResources","translation":"You received this application for leave of {0} for information.","translationDE":"Du erhählst diesen Urlaubseintrag von {0} zur Information.","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"vacation.mail.reason.own","bundleName":"I18nResources","translation":"This is your application for leave ({0}).","translationDE":"Es ist dein Urlaubseintrag.","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"vacation.mail.reason.replacement","bundleName":"I18nResources","translation":"You were chosen as substitute for this leave. If you can''t substitute your colleague, please contact {0}.","translationDE":"Du wurdest als Vertretung ausgewählt. Bitte bearbeite diesen Eintrag oder nimm Kontakt zu {0} auf.","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"vacation.mail.reason.replacement","bundleName":"I18nResources","translation":"You were chosen as substitute for this leave. If you can''t substitute your colleague, please contact {0}.","translationDE":"Du wurdest als Vertretung ausgewählt. Bitte bearbeite diesen Eintrag oder nimm Kontakt zu {0} auf.","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"vacation.manager","bundleName":"I18nResources","translation":"Manager","translationDE":"Abstimmung","usedInClasses":["org.projectforge.business.vacation.model.VacationDO","org.projectforge.business.vacation.model.VacationMode","org.projectforge.rest.VacationAccountPageRest","org.projectforge.rest.VacationPagesRest"],"usedInFiles":["./projectforge-business/src/main/resources/mail/vacationMail.html"]}, - {"i18nKey":"vacation.neededdays","bundleName":"I18nResources","translation":"Needed vacation days","translationDE":"Benötigte Urlaubstage","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"vacation.neededdays","bundleName":"I18nResources","translation":"Needed vacation days","translationDE":"Benötigte Urlaubstage","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"vacation.other","bundleName":"I18nResources","translation":"Other","translationDE":"Anderer","usedInClasses":["org.projectforge.business.vacation.model.VacationMode"],"usedInFiles":[]}, {"i18nKey":"vacation.own","bundleName":"I18nResources","translation":"Own","translationDE":"Eigner:in","usedInClasses":["org.projectforge.business.vacation.model.VacationMode"],"usedInFiles":[]}, - {"i18nKey":"vacation.plandannualleave","bundleName":"I18nResources","translation":"Number of planned leave days for the rest of the year","translationDE":"Anzahl geplanter Urlaubstage für restliches Kalenderjahr","usedInClasses":["org.projectforge.web.fibu.MonthlyEmployeeReportPage"],"usedInFiles":[]}, + {"i18nKey":"vacation.plandannualleave","bundleName":"I18nResources","translation":"Number of planned leave days for the rest of the year","translationDE":"Anzahl geplanter Urlaubstage für restliches Kalenderjahr","usedInClasses":["org.projectforge.web.fibu.MonthlyEmployeeReportPage"],"usedInFiles":[]}, {"i18nKey":"vacation.previousyearleave","bundleName":"I18nResources","translation":"Previous year leave","translationDE":"Resturlaub aus dem Vorjahr","usedInClasses":["org.projectforge.business.vacation.model.RemainingLeaveDO","org.projectforge.rest.VacationAccountPageRest"],"usedInFiles":[]}, {"i18nKey":"vacation.previousyearleaveunused","bundleName":"I18nResources","translation":"Unused previous year leave until {0}","translationDE":"Ungenutzer Resturlaub zum {0}","usedInClasses":["org.projectforge.rest.VacationAccountPageRest"],"usedInFiles":[]}, {"i18nKey":"vacation.previousyearleaveused","bundleName":"I18nResources","translation":"Previous year leave used","translationDE":"Genutzter Resturlaub aus dem Vorjahr","usedInClasses":[],"usedInFiles":[]}, @@ -2649,9 +2703,9 @@ {"i18nKey":"vacation.remainingLeaveFromYearUnused","bundleName":"I18nResources","translation":"Unused remaining leave from {0}.","translationDE":"Ungenutzer Resturlaub aus dem Jahr {0}","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"vacation.replacement","bundleName":"I18nResources","translation":"Substitution","translationDE":"Vertretung","usedInClasses":["org.projectforge.business.vacation.model.VacationDO","org.projectforge.business.vacation.model.VacationMode","org.projectforge.rest.VacationAccountPageRest","org.projectforge.rest.VacationPagesRest","org.projectforge.rest.calendar.VacationProvider"],"usedInFiles":["./projectforge-business/src/main/resources/mail/vacationMail.html"]}, {"i18nKey":"vacation.replacement.others","bundleName":"I18nResources","translation":"Other substitutes","translationDE":"Weitere Vertretungen","usedInClasses":["org.projectforge.business.vacation.model.VacationDO","org.projectforge.rest.VacationAccountPageRest"],"usedInFiles":["./projectforge-business/src/main/resources/mail/vacationMail.html"]}, - {"i18nKey":"vacation.setStartAndEndFirst","bundleName":"I18nResources","translation":"","translationDE":"","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"vacation.setStartAndEndFirst","bundleName":"I18nResources","translation":"","translationDE":"","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"vacation.special","bundleName":"I18nResources","translation":"Special leave","translationDE":"Sonderurlaub","usedInClasses":["org.projectforge.business.vacation.model.VacationDO","org.projectforge.rest.VacationAccountPageRest","org.projectforge.rest.VacationPagesRest"],"usedInFiles":["./projectforge-business/src/main/resources/mail/vacationMail.html"]}, - {"i18nKey":"vacation.special.tooltip","bundleName":"I18nResources","translation":"Special leave days will be excluded from the vacation account.","translationDE":"Sonderurlaub wird im Urlaubskonto nicht berücksichtigt.","usedInClasses":["org.projectforge.business.vacation.model.VacationDO"],"usedInFiles":[]}, + {"i18nKey":"vacation.special.tooltip","bundleName":"I18nResources","translation":"Special leave days will be excluded from the vacation account.","translationDE":"Sonderurlaub wird im Urlaubskonto nicht berücksichtigt.","usedInClasses":["org.projectforge.business.vacation.model.VacationDO"],"usedInFiles":[]}, {"i18nKey":"vacation.specialApproved","bundleName":"I18nResources","translation":"Special leave approved","translationDE":"Abgestimmter Sonderurlaub","usedInClasses":["org.projectforge.rest.VacationAccountPageRest"],"usedInFiles":[]}, {"i18nKey":"vacation.specialInProgress","bundleName":"I18nResources","translation":"Special leave in progress","translationDE":"Sonderurlaub in Abstimmung","usedInClasses":["org.projectforge.rest.VacationAccountPageRest"],"usedInFiles":[]}, {"i18nKey":"vacation.specialVacationInYearApproved","bundleName":"I18nResources","translation":"Approved special leave in {0}","translationDE":"Abgestimmter Sonderurlaub in {0}","usedInClasses":[],"usedInFiles":[]}, @@ -2662,12 +2716,12 @@ {"i18nKey":"vacation.status.approved","bundleName":"I18nResources","translation":"Approved","translationDE":"Abgestimmt","usedInClasses":["org.projectforge.business.vacation.model.VacationStatus","org.projectforge.business.vacation.service.VacationExcelExporter"],"usedInFiles":[]}, {"i18nKey":"vacation.status.inProgress","bundleName":"I18nResources","translation":"In progress","translationDE":"In Abstimmung","usedInClasses":["org.projectforge.business.vacation.model.VacationStatus","org.projectforge.business.vacation.service.VacationExcelExporter"],"usedInFiles":[]}, {"i18nKey":"vacation.status.rejected","bundleName":"I18nResources","translation":"Rejected","translationDE":"Abgelehnt","usedInClasses":["org.projectforge.business.vacation.model.VacationStatus"],"usedInFiles":[]}, - {"i18nKey":"vacation.subscription","bundleName":"I18nResources","translation":"Subscription of employee leave days","translationDE":"Abonnieren von Urlaubseinträgen","usedInClasses":["org.projectforge.rest.VacationAccountPageRest"],"usedInFiles":[]}, - {"i18nKey":"vacation.subscription.info","bundleName":"I18nResources","translation":"Leave days are displayed on demand in the calendar view. They also may be defined as calendars (see list of calendars) for users and/or groups. These calendars can be subscribed for usage in 3rd party calendar tools.","translationDE":"Urlaubseinträge können in der Kalendersicht eingesehen werden, aber auch als Kalender (s. Kalenderliste) für bestimmte Benutzer:innen oder Gruppen angelegt und auch abonniert werden. Sie können insbesondere die eigenen Urlaube und die von Kollegen oder Kolleginnen/Teams in die eigene Kalendersoftware integriert werden.","usedInClasses":["org.projectforge.rest.VacationAccountPageRest"],"usedInFiles":[]}, + {"i18nKey":"vacation.subscription","bundleName":"I18nResources","translation":"Subscription of employee leave days","translationDE":"Abonnieren von Urlaubseinträgen","usedInClasses":["org.projectforge.rest.VacationAccountPageRest"],"usedInFiles":[]}, + {"i18nKey":"vacation.subscription.info","bundleName":"I18nResources","translation":"Leave days are displayed on demand in the calendar view. They also may be defined as calendars (see list of calendars) for users and/or groups. These calendars can be subscribed for usage in 3rd party calendar tools.","translationDE":"Urlaubseinträge können in der Kalendersicht eingesehen werden, aber auch als Kalender (s. Kalenderliste) für bestimmte Benutzer:innen oder Gruppen angelegt und auch abonniert werden. Sie können insbesondere die eigenen Urlaube und die von Kollegen oder Kolleginnen/Teams in die eigene Kalendersoftware integriert werden.","usedInClasses":["org.projectforge.rest.VacationAccountPageRest"],"usedInFiles":[]}, {"i18nKey":"vacation.subtotal","bundleName":"I18nResources","translation":"Subtotal","translationDE":"Zwischensumme","usedInClasses":["org.projectforge.rest.VacationAccountPageRest"],"usedInFiles":[]}, {"i18nKey":"vacation.title.add","bundleName":"I18nResources","translation":"Create leave application","translationDE":"Urlaubseintrag erstellen","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"vacation.title.edit","bundleName":"I18nResources","translation":"Edit leave application","translationDE":"Urlaubseintrag ändern","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"vacation.title.heading","bundleName":"I18nResources","translation":"Leave_Applications","translationDE":"Urlaubseinträge","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"vacation.title.edit","bundleName":"I18nResources","translation":"Edit leave application","translationDE":"Urlaubseintrag ändern","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"vacation.title.heading","bundleName":"I18nResources","translation":"Leave_Applications","translationDE":"Urlaubseinträge","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"vacation.title.list","bundleName":"I18nResources","translation":"Leave application list","translationDE":"Urlaubseintragsliste","usedInClasses":["org.projectforge.rest.VacationAccountPageRest"],"usedInFiles":[]}, {"i18nKey":"vacation.usedvacation","bundleName":"I18nResources","translation":"Used vacation","translationDE":"Bereits verwendeter Urlaub","usedInClasses":[],"usedInFiles":[]}, {"i18nKey":"vacation.vacationApproved","bundleName":"I18nResources","translation":"Approved Vacation","translationDE":"Abgestimmter Urlaub","usedInClasses":["org.projectforge.rest.VacationAccountPageRest"],"usedInFiles":[]}, @@ -2676,16 +2730,16 @@ {"i18nKey":"vacation.vacationer","bundleName":"I18nResources","translation":"Vacationer","translationDE":"Urlauber","usedInClasses":[],"usedInFiles":["./projectforge-business/src/main/resources/mail/vacationMail.html"]}, {"i18nKey":"vacation.vacationmode","bundleName":"I18nResources","translation":"Assignment","translationDE":"Zuordnung","usedInClasses":["org.projectforge.business.vacation.model.VacationDO","org.projectforge.rest.VacationAccountPageRest","org.projectforge.rest.VacationPagesRest"],"usedInFiles":[]}, {"i18nKey":"vacation.vacationsOfReplacements","bundleName":"I18nResources","translation":"Vacations of substitutes","translationDE":"Urlaube der Vertretungen","usedInClasses":["org.projectforge.rest.VacationPagesRest"],"usedInFiles":[]}, - {"i18nKey":"vacation.validate.datenotset","bundleName":"I18nResources","translation":"Start- and end-date are required.","translationDE":"Das Start- und das Enddatum müssen angegeben sein.","usedInClasses":["org.projectforge.business.vacation.service.VacationValidator"],"usedInFiles":[]}, + {"i18nKey":"vacation.validate.datenotset","bundleName":"I18nResources","translation":"Start- and end-date are required.","translationDE":"Das Start- und das Enddatum müssen angegeben sein.","usedInClasses":["org.projectforge.business.vacation.service.VacationValidator"],"usedInFiles":[]}, {"i18nKey":"vacation.validate.daysarenull","bundleName":"I18nResources","translation":"vacation with 0 workdays","translationDE":"Urlaubseintrag mit 0 Arbeitstagen","usedInClasses":["org.projectforge.business.vacation.service.VacationValidator"],"usedInFiles":[]}, - {"i18nKey":"vacation.validate.endbeforestart","bundleName":"I18nResources","translation":"The choosen end date is before start date","translationDE":"Das gewählte Enddatum liegt vor dem Startdatum","usedInClasses":["org.projectforge.business.vacation.service.VacationValidator"],"usedInFiles":[]}, - {"i18nKey":"vacation.validate.leaveapplicationexists","bundleName":"I18nResources","translation":"A leave application already exists for this period","translationDE":"Für den ausgewählten Zeitraum existiert bereits ein Urlaubseintrag","usedInClasses":["org.projectforge.business.vacation.service.VacationValidator"],"usedInFiles":[]}, - {"i18nKey":"vacation.validate.noCalender","bundleName":"I18nResources","translation":"The company vacation calendar {0} must be selected","translationDE":"Der Firmenurlaubskalender {0} muss ausgewählt werden","usedInClasses":[],"usedInFiles":[]}, - {"i18nKey":"vacation.validate.notAllowedToSelfApprove","bundleName":"I18nResources","translation":"You're not allowed to approve this entry.","translationDE":"Sie können diesen Eintrag nicht als abgestimmt markieren.","usedInClasses":["org.projectforge.business.vacation.service.VacationValidator"],"usedInFiles":[]}, - {"i18nKey":"vacation.validate.notEnoughVacationDaysLeft","bundleName":"I18nResources","translation":"There are not enough free vacation days left","translationDE":"Es stehen nicht mehr genügend Urlaubstage zur Verfügung","usedInClasses":["org.projectforge.business.vacation.service.VacationValidator"],"usedInFiles":[]}, - {"i18nKey":"vacation.validate.startDateBeforeNow","bundleName":"I18nResources","translation":"The chosen start date is before now. Only HR employees can create data before now.","translationDE":"Das gewählte Startdatum liegt vor dem aktuellen Datum. Nur HR-Mitarbeiter:innen können Daten in der Vergangenheit anlegen.","usedInClasses":["org.projectforge.business.vacation.service.VacationValidator"],"usedInFiles":[]}, - {"i18nKey":"vacation.validate.usedBiggerThanPreviousYear","bundleName":"I18nResources","translation":"Count of used vacation days is bigger than count of days which are taken from last year vacation.","translationDE":"Die Anzahl der verbrauchten Tage aus dem Urlaub vom Vorjahr ist größer als die tatsächliche Anzahl der Urlaubstage aus dem Vorjahr.","usedInClasses":["org.projectforge.web.fibu.EmployeeVacationFormValidator"],"usedInFiles":[]}, - {"i18nKey":"vacation.validate.usedButNoPreviousYear","bundleName":"I18nResources","translation":"You only have used days for previous year entered but not how many days there was taken from previous year.","translationDE":"Es wurden nur die verbrauchten Tage des Urlaubs aus dem Vorjahr angegeben, aber nicht die tatsächlichen.","usedInClasses":["org.projectforge.web.fibu.EmployeeVacationFormValidator"],"usedInFiles":[]}, + {"i18nKey":"vacation.validate.endbeforestart","bundleName":"I18nResources","translation":"The choosen end date is before start date","translationDE":"Das gewählte Enddatum liegt vor dem Startdatum","usedInClasses":["org.projectforge.business.vacation.service.VacationValidator"],"usedInFiles":[]}, + {"i18nKey":"vacation.validate.leaveapplicationexists","bundleName":"I18nResources","translation":"A leave application already exists for this period","translationDE":"Für den ausgewählten Zeitraum existiert bereits ein Urlaubseintrag","usedInClasses":["org.projectforge.business.vacation.service.VacationValidator"],"usedInFiles":[]}, + {"i18nKey":"vacation.validate.noCalender","bundleName":"I18nResources","translation":"The company vacation calendar {0} must be selected","translationDE":"Der Firmenurlaubskalender {0} muss ausgewählt werden","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"vacation.validate.notAllowedToSelfApprove","bundleName":"I18nResources","translation":"You're not allowed to approve this entry.","translationDE":"Sie können diesen Eintrag nicht als abgestimmt markieren.","usedInClasses":["org.projectforge.business.vacation.service.VacationValidator"],"usedInFiles":[]}, + {"i18nKey":"vacation.validate.notEnoughVacationDaysLeft","bundleName":"I18nResources","translation":"There are not enough free vacation days left","translationDE":"Es stehen nicht mehr genügend Urlaubstage zur Verfügung","usedInClasses":["org.projectforge.business.vacation.service.VacationValidator"],"usedInFiles":[]}, + {"i18nKey":"vacation.validate.startDateBeforeNow","bundleName":"I18nResources","translation":"The chosen start date is before now. Only HR employees can create data before now.","translationDE":"Das gewählte Startdatum liegt vor dem aktuellen Datum. Nur HR-Mitarbeiter:innen können Daten in der Vergangenheit anlegen.","usedInClasses":["org.projectforge.business.vacation.service.VacationValidator"],"usedInFiles":[]}, + {"i18nKey":"vacation.validate.usedBiggerThanPreviousYear","bundleName":"I18nResources","translation":"Count of used vacation days is bigger than count of days which are taken from last year vacation.","translationDE":"Die Anzahl der verbrauchten Tage aus dem Urlaub vom Vorjahr ist größer als die tatsächliche Anzahl der Urlaubstage aus dem Vorjahr.","usedInClasses":["org.projectforge.web.fibu.EmployeeVacationFormValidator"],"usedInFiles":[]}, + {"i18nKey":"vacation.validate.usedButNoPreviousYear","bundleName":"I18nResources","translation":"You only have used days for previous year entered but not how many days there was taken from previous year.","translationDE":"Es wurden nur die verbrauchten Tage des Urlaubs aus dem Vorjahr angegeben, aber nicht die tatsächlichen.","usedInClasses":["org.projectforge.web.fibu.EmployeeVacationFormValidator"],"usedInFiles":[]}, {"i18nKey":"vacation.validate.vacationBeforeJoinDate","bundleName":"I18nResources","translation":"Vacations before joining of an employee are not allowed.","translationDE":"Der Urlaub darf nicht vor dem Eintrittsdatum des Mitarbeiters liegen.","usedInClasses":["org.projectforge.business.vacation.service.VacationValidator"],"usedInFiles":[]}, {"i18nKey":"vacation.workingdays","bundleName":"I18nResources","translation":"Workdays","translationDE":"Anzahl Urlaubstage","usedInClasses":["org.projectforge.rest.VacationPagesRest"],"usedInFiles":["./projectforge-business/src/main/resources/mail/vacationMail.html"]}, {"i18nKey":"validation.error.fieldRequired","bundleName":"I18nResources","translation":"Field ''{0}'' is required.","translationDE":"Feld ''{0}'' muss ausgefüllt werden.","usedInClasses":["org.projectforge.framework.i18n.RequiredFieldIsEmptyException","org.projectforge.plugins.merlin.MerlinVariable","org.projectforge.rest.AddressPagesRest","org.projectforge.rest.core.AbstractPagesRest","org.projectforge.ui.ValidationError","org.projectforge.web.wicket.AbstractForm","org.projectforge.web.wicket.WebConstants"],"usedInFiles":[]}, @@ -2699,14 +2753,14 @@ {"i18nKey":"value","bundleName":"I18nResources","translation":"Value","translationDE":"Wert","usedInClasses":["org.projectforge.business.fibu.kost.BusinessAssessmentRowConfig","org.projectforge.business.user.UserRightVO","org.projectforge.flyway.dbmigration.V7_0_0_11__MigrateEmployeeAnnualLeaveDays","org.projectforge.flyway.dbmigration.V7_0_0_6__MigrateEmployeeAndCarryVacationDays","org.projectforge.framework.persistence.history.HistoryFormatUserAdapter","org.projectforge.framework.persistence.user.entities.UserPrefEntryDO","org.projectforge.framework.persistence.user.entities.UserRightDO","org.projectforge.framework.utils.KeyValueBean","org.projectforge.framework.utils.LabelValueBean","org.projectforge.plugins.marketing.AddressCampaignValueDO","org.projectforge.plugins.marketing.AddressCampaignValueDao","org.projectforge.plugins.marketing.AddressCampaignValueEditForm","org.projectforge.plugins.marketing.AddressCampaignValueExport","org.projectforge.plugins.marketing.AddressCampaignValueListForm","org.projectforge.plugins.marketing.AddressCampaignValueListPage","org.projectforge.plugins.marketing.rest.AddressCampaignValueMultiSelectedPageRest","org.projectforge.plugins.marketing.rest.AddressCampaignValuePagesRest","org.projectforge.rest.core.RestResolver","org.projectforge.rest.dvelop.DvelopClient","org.projectforge.ui.UISelect","org.projectforge.web.admin.ConfigurationListPage","org.projectforge.web.core.importstorage.AbstractImportStoragePanel","org.projectforge.web.teamcal.event.MyWicketEvent","org.projectforge.web.user.UserEditForm","org.projectforge.web.user.UserPrefEditForm"],"usedInFiles":["./projectforge-business/src/main/resources/mail/orderChangeNotification.html","./projectforge-business/src/main/resources/mail/todoChangeNotification.html","./projectforge-business/src/main/resources/mail/vacationMail.html","./projectforge-wicket/src/main/java/org/projectforge/web/core/importstorage/AbstractImportStoragePanel.html"]}, {"i18nKey":"values","bundleName":"I18nResources","translation":"Values","translationDE":"Werte","usedInClasses":["org.projectforge.business.ldap.LdapGroupValues","org.projectforge.business.ldap.LdapUserValues","org.projectforge.plugins.marketing.AddressCampaignDO","org.projectforge.plugins.marketing.AddressCampaignEditForm","org.projectforge.plugins.marketing.AddressCampaignListPage","org.projectforge.plugins.marketing.rest.AddressCampaignPagesRest","org.projectforge.rest.json.UISelectTypeSerializer"],"usedInFiles":[]}, {"i18nKey":"webauthn.entry.displayName","bundleName":"I18nResources","translation":"Display name","translationDE":"Anzeigename","usedInClasses":["org.projectforge.rest.my2fa.WebAuthnEntryPageRest","org.projectforge.security.webauthn.WebAuthnEntryDO"],"usedInFiles":[]}, - {"i18nKey":"webauthn.entry.displayName.info","bundleName":"I18nResources","translation":"Feel free to enter any name to identify your WebAuthn token.","translationDE":"Hier kannst du einen beliebigen Namen eingeben, um deinen Token leichter zuordnen zu können.","usedInClasses":["org.projectforge.rest.my2fa.WebAuthnEntryPageRest"],"usedInFiles":[]}, - {"i18nKey":"webauthn.entry.edit","bundleName":"I18nResources","translation":"Edit WebAuthn entry","translationDE":"WebAuthn-Eintrag ändern","usedInClasses":["org.projectforge.rest.my2fa.WebAuthnEntryPageRest"],"usedInFiles":[]}, - {"i18nKey":"webauthn.entry.signCount","bundleName":"I18nResources","translation":"Sign count","translationDE":"Signaturzähler","usedInClasses":["org.projectforge.rest.my2fa.WebAuthnEntryPageRest","org.projectforge.security.webauthn.WebAuthnEntryDO"],"usedInFiles":[]}, - {"i18nKey":"webauthn.entry.signCount.info","bundleName":"I18nResources","translation":"Counter of authentications (will be incremented on every token usage)","translationDE":"Zähler für Authentifizierungen (wird nach jeder Benutzung hochgezählt)","usedInClasses":["org.projectforge.rest.my2fa.WebAuthnEntryPageRest","org.projectforge.security.webauthn.WebAuthnEntryDO"],"usedInFiles":[]}, + {"i18nKey":"webauthn.entry.displayName.info","bundleName":"I18nResources","translation":"Feel free to enter any name to identify your WebAuthn token.","translationDE":"Hier kannst du einen beliebigen Namen eingeben, um deinen Token leichter zuordnen zu können.","usedInClasses":["org.projectforge.rest.my2fa.WebAuthnEntryPageRest"],"usedInFiles":[]}, + {"i18nKey":"webauthn.entry.edit","bundleName":"I18nResources","translation":"Edit WebAuthn entry","translationDE":"WebAuthn-Eintrag ändern","usedInClasses":["org.projectforge.rest.my2fa.WebAuthnEntryPageRest"],"usedInFiles":[]}, + {"i18nKey":"webauthn.entry.signCount","bundleName":"I18nResources","translation":"Sign count","translationDE":"Signaturzähler","usedInClasses":["org.projectforge.rest.my2fa.WebAuthnEntryPageRest","org.projectforge.security.webauthn.WebAuthnEntryDO"],"usedInFiles":[]}, + {"i18nKey":"webauthn.entry.signCount.info","bundleName":"I18nResources","translation":"Counter of authentications (will be incremented on every token usage)","translationDE":"Zähler für Authentifizierungen (wird nach jeder Benutzung hochgezählt)","usedInClasses":["org.projectforge.rest.my2fa.WebAuthnEntryPageRest","org.projectforge.security.webauthn.WebAuthnEntryDO"],"usedInFiles":[]}, {"i18nKey":"webauthn.error.process","bundleName":"I18nResources","translation":"Error while processing authentication.","translationDE":"Fehler im Authentifizierungsprozess.","usedInClasses":["org.projectforge.security.webauthn.WebAuthnSupport"],"usedInFiles":[]}, - {"i18nKey":"webauthn.error.userNotOwnerOrEntryDoesnotExist","bundleName":"I18nResources","translation":"The user has no access to this foreign webauthn entry or no such entry exists.","translationDE":"Der/die Benutzer:in hat nicht das Recht, auf fremde WebAuthn-Einträge zuzugreifen oder der gesuchte Eintrag existiert nicht.","usedInClasses":["org.projectforge.security.webauthn.WebAuthnEntryDao"],"usedInFiles":[]}, + {"i18nKey":"webauthn.error.userNotOwnerOrEntryDoesnotExist","bundleName":"I18nResources","translation":"The user has no access to this foreign webauthn entry or no such entry exists.","translationDE":"Der/die Benutzer:in hat nicht das Recht, auf fremde WebAuthn-Einträge zuzugreifen oder der gesuchte Eintrag existiert nicht.","usedInClasses":["org.projectforge.security.webauthn.WebAuthnEntryDao"],"usedInFiles":[]}, {"i18nKey":"webauthn.error.validate","bundleName":"I18nResources","translation":"Error while validating authentication.","translationDE":"Fehler bei der Validierung der Authentifizierungsdaten.","usedInClasses":["org.projectforge.security.webauthn.WebAuthnSupport"],"usedInFiles":[]}, - {"i18nKey":"webauthn.info","bundleName":"I18nResources","translation":"WebAuthn is a modern and secure standard for having secure 2FA hardware tokens. You may here register your hardware tokens.","translationDE":"WebAuthn ist ein moderner und sicherer Standard, um eine hardwarebasierte Zweifaktorauthentifizierung zu ermöglichen. Hier kannst du einen oder mehrere deiner Token registrieren, um eine bequeme und sichere 2FA zu nutzen.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, + {"i18nKey":"webauthn.info","bundleName":"I18nResources","translation":"WebAuthn is a modern and secure standard for having secure 2FA hardware tokens. You may here register your hardware tokens.","translationDE":"WebAuthn ist ein moderner und sicherer Standard, um eine hardwarebasierte Zweifaktorauthentifizierung zu ermöglichen. Hier kannst du einen oder mehrere deiner Token registrieren, um eine bequeme und sichere 2FA zu nutzen.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, {"i18nKey":"webauthn.registration.2FARequired.info","bundleName":"I18nResources","translation":"Please renew Your 2FA before registering Your WebAuthn token.","translationDE":"Bitte de 2FA erneuern, bevor ein WebAuthn-Token registriert werden kann.","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, {"i18nKey":"webauthn.registration.button.authenticate","bundleName":"I18nResources","translation":"WebAuthn","translationDE":"WebAuthn","usedInClasses":["org.projectforge.security.WebAuthnServicesRest"],"usedInFiles":[]}, {"i18nKey":"webauthn.registration.button.authenticate.info","bundleName":"I18nResources","translation":"You may use any of your registered WebAuthn tokens here (for example Yubikey).","translationDE":"Du kannst hier registrierte WebAuthn-Token benutzen (z. B. Yubikey).","usedInClasses":["org.projectforge.security.WebAuthnServicesRest"],"usedInFiles":[]}, @@ -2714,7 +2768,7 @@ {"i18nKey":"webauthn.title","bundleName":"I18nResources","translation":"WebAuthn (Fido2 etc.)","translationDE":"WebAuthn (Fido2 etc.)","usedInClasses":["org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]}, {"i18nKey":"weekOfYear","bundleName":"I18nResources","translation":"Week of year","translationDE":"Kalenderwoche","usedInClasses":["org.projectforge.rest.TimesheetPagesRest","org.projectforge.rest.calendar.CalendarSubscriptionInfoPageRest","org.projectforge.rest.pub.CalendarSubscriptionServiceRest"],"usedInFiles":[]}, {"i18nKey":"wizard","bundleName":"I18nResources","translation":"Wizard","translationDE":"Assistent","usedInClasses":["org.projectforge.rest.task.TaskWizardPageRest","org.projectforge.web.admin.TaskWizardForm","org.projectforge.web.task.TaskTreePage"],"usedInFiles":[]}, - {"i18nKey":"yes","bundleName":"I18nResources","translation":"Yes","translationDE":"Ja","usedInClasses":["org.projectforge.business.vacation.service.VacationSendMailService","org.projectforge.framework.xmlstream.XmlHelper","org.projectforge.rest.TimesheetPagesRest","org.projectforge.rest.TokenInfoPageRest","org.projectforge.rest.dto.Vacation","org.projectforge.rest.my2fa.WebAuthnEntryPageRest","org.projectforge.ui.LayoutUtils","org.projectforge.ui.UIAttachmentList","org.projectforge.ui.UIButton","org.projectforge.web.dialog.ModalQuestionDialog"],"usedInFiles":["./projectforge-business/src/main/kotlin/org/projectforge/framework/i18n/I18n.kt"]}, + {"i18nKey":"yes","bundleName":"I18nResources","translation":"Yes","translationDE":"Ja","usedInClasses":["org.projectforge.business.vacation.service.VacationSendMailService","org.projectforge.framework.xmlstream.XmlHelper","org.projectforge.rest.TimesheetPagesRest","org.projectforge.rest.TokenInfoPageRest","org.projectforge.rest.dto.Vacation","org.projectforge.rest.my2fa.WebAuthnEntryPageRest","org.projectforge.rest.poll.PollPageRest","org.projectforge.ui.LayoutUtils","org.projectforge.ui.UIAttachmentList","org.projectforge.ui.UIButton","org.projectforge.web.dialog.ModalQuestionDialog"],"usedInFiles":["./projectforge-business/src/main/kotlin/org/projectforge/framework/i18n/I18n.kt"]}, {"i18nKey":"plugins.licensemanagement.device","bundleName":"LicenseManagementI18nResources","translation":"Device","translationDE":"Gerät","usedInClasses":["org.projectforge.plugins.licensemanagement.LicenseDO","org.projectforge.plugins.licensemanagement.LicenseEditForm","org.projectforge.plugins.licensemanagement.LicenseListPage"],"usedInFiles":[]}, {"i18nKey":"plugins.licensemanagement.device.tooltip","bundleName":"LicenseManagementI18nResources","translation":"On which device(s) is this software installed?","translationDE":"Auf welchem/welchen Gerät(en) ist die Software installiert?","usedInClasses":["org.projectforge.plugins.licensemanagement.LicenseEditForm"],"usedInFiles":[]}, {"i18nKey":"plugins.licensemanagement.file1","bundleName":"LicenseManagementI18nResources","translation":"File 1","translationDE":"Datei 1","usedInClasses":["org.projectforge.plugins.licensemanagement.LicenseEditForm"],"usedInFiles":[]}, @@ -2996,6 +3050,14 @@ {"i18nKey":"plugins.teamcal.title.list.select","bundleName":null,"translation":null,"translationDE":null,"usedInClasses":["org.projectforge.web.teamcal.admin.TeamCalEditPage","org.projectforge.web.teamcal.admin.TeamCalListPage"],"usedInFiles":[]}, {"i18nKey":"plugins.teamcalendar.event","bundleName":null,"translation":null,"translationDE":null,"usedInClasses":["org.projectforge.business.user.UserRightId"],"usedInFiles":[]}, {"i18nKey":"plugins.todo.title.list.select","bundleName":null,"translation":null,"translationDE":null,"usedInClasses":["org.projectforge.plugins.todo.ToDoEditPage","org.projectforge.plugins.todo.ToDoListPage"],"usedInFiles":[]}, + {"i18nKey":"poll.attendee_groups","bundleName":null,"translation":null,"translationDE":null,"usedInClasses":["org.projectforge.business.poll.PollDO"],"usedInFiles":[]}, + {"i18nKey":"poll.error.closed","bundleName":null,"translation":null,"translationDE":"Umfrage wurde bereits beendet","usedInClasses":[],"usedInFiles":[]}, + {"i18nKey":"poll.full_access_groups","bundleName":null,"translation":null,"translationDE":null,"usedInClasses":["org.projectforge.business.poll.PollDO"],"usedInFiles":[]}, + {"i18nKey":"poll.full_access_user","bundleName":null,"translation":null,"translationDE":null,"usedInClasses":["org.projectforge.business.poll.PollDO"],"usedInFiles":[]}, + {"i18nKey":"poll.inputFields","bundleName":null,"translation":null,"translationDE":null,"usedInClasses":["org.projectforge.business.poll.PollDO"],"usedInFiles":[]}, + {"i18nKey":"poll.response.owner","bundleName":null,"translation":null,"translationDE":null,"usedInClasses":["org.projectforge.business.poll.PollResponseDO"],"usedInFiles":[]}, + {"i18nKey":"poll.response.poll","bundleName":null,"translation":null,"translationDE":null,"usedInClasses":["org.projectforge.business.poll.PollResponseDO"],"usedInFiles":[]}, + {"i18nKey":"poll.responses","bundleName":null,"translation":null,"translationDE":null,"usedInClasses":["org.projectforge.business.poll.PollResponseDO"],"usedInFiles":[]}, {"i18nKey":"timesheet.title.list.select","bundleName":null,"translation":null,"translationDE":null,"usedInClasses":["org.projectforge.web.timesheet.TimesheetEditPage","org.projectforge.web.timesheet.TimesheetListPage"],"usedInFiles":[]}, {"i18nKey":"userPref.title.list.select","bundleName":null,"translation":null,"translationDE":null,"usedInClasses":["org.projectforge.web.user.UserPrefEditPage","org.projectforge.web.user.UserPrefListPage"],"usedInFiles":[]} ] diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index b5fbc1fd17..dbc6b03e50 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -1,8 +1,32 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.business.poll import org.hibernate.search.annotations.Indexed import org.projectforge.business.poll.filter.PollAssignment import org.projectforge.business.poll.filter.PollState +import org.projectforge.common.StringHelper import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.persistence.api.AUserRightId import org.projectforge.framework.persistence.entities.DefaultBaseDO @@ -13,7 +37,6 @@ import java.time.LocalDate import javax.persistence.* -@Suppress("UNREACHABLE_CODE") @Entity @Indexed @Table(name = "t_poll") @@ -77,6 +100,10 @@ open class PollDO : DefaultBaseDO() { if (currentUserId == this.owner?.id) { assignmentList.add(PollAssignment.OWNER) } + val accessUserIds = toIntArray(this.fullAccessUserIds) + if (accessUserIds?.contains(currentUserId) == true) { + assignmentList.add(PollAssignment.ACCESS) + } if (!this.fullAccessUserIds.isNullOrBlank()) { val accessUserIds = this.fullAccessUserIds!!.split(", ").map { it.toInt() }.toIntArray() if (accessUserIds.contains(currentUserId)) { @@ -107,4 +134,11 @@ open class PollDO : DefaultBaseDO() { enum class State { RUNNING, FINISHED } -} \ No newline at end of file + + companion object { + internal fun toIntArray(str: String?): IntArray? { + if (str.isNullOrBlank()) return null + return StringHelper.splitToInts(str, ",", false) + } + } +} diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index e7cd2cc46f..844be2ed69 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -1,3 +1,26 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.business.poll import org.projectforge.business.group.service.GroupService @@ -13,7 +36,7 @@ import org.springframework.stereotype.Repository open class PollDao : BaseDao(PollDO::class.java) { @Autowired - private val groupService: GroupService? = null + private lateinit var groupService: GroupService override fun newInstance(): PollDO { return PollDO() @@ -30,8 +53,8 @@ open class PollDao : BaseDao(PollDO::class.java) { if (obj == null && operationType == OperationType.SELECT) { return true }; - if (obj != null && operationType == OperationType.SELECT){ - if(hasFullAccess(obj) || isAttendee(obj, ThreadLocalUserContext.user?.id!!)) + if (obj != null && operationType == OperationType.SELECT) { + if (hasFullAccess(obj) || isAttendee(obj, ThreadLocalUserContext.user?.id!!)) return true } if (obj != null) { @@ -48,7 +71,7 @@ open class PollDao : BaseDao(PollDO::class.java) { return true if (!obj.fullAccessGroupIds.isNullOrBlank()) { val groupIdArray = obj.fullAccessGroupIds!!.split(", ").map { it.toInt() }.toIntArray() - val groupUsers = groupService?.getGroupUsers(groupIdArray) + val groupUsers = groupService.getGroupUsers(groupIdArray) if (groupUsers?.contains(loggedInUser) == true) return true } @@ -61,4 +84,4 @@ open class PollDao : BaseDao(PollDO::class.java) { return true return false } -} \ No newline at end of file +} diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt index 2775557ec7..23f105a7a0 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt @@ -1,3 +1,26 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.business.poll import org.hibernate.search.annotations.Indexed @@ -28,4 +51,4 @@ open class PollResponseDO : DefaultBaseDO() { @PropertyInfo(i18nKey = "poll.responses") @get:Column(name = "responses", nullable = true, length = 1000) open var responses: String? = null -} \ No newline at end of file +} diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt index 66cd987917..3fb7cb0170 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDao.kt @@ -1,3 +1,26 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.business.poll import org.projectforge.framework.access.OperationType @@ -20,4 +43,4 @@ open class PollResponseDao : BaseDao(PollResponseDO::class.java) ): Boolean { return true } -} \ No newline at end of file +} diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignment.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignment.kt index 7b76c8bd6f..23691b54dd 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignment.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignment.kt @@ -1,3 +1,26 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.business.poll.filter import org.projectforge.common.i18n.I18nEnum @@ -7,4 +30,4 @@ enum class PollAssignment(val key: String) : I18nEnum { override val i18nKey: String get() = ("poll.$key") -} \ No newline at end of file +} diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignmentFilter.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignmentFilter.kt index f4c15a2dab..bc20b61c7b 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignmentFilter.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollAssignmentFilter.kt @@ -1,3 +1,26 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.business.poll.filter import org.projectforge.business.group.service.GroupService @@ -38,4 +61,4 @@ class PollAssignmentFilter(val values: List) : CustomResultFilte return _groupService!! } } -} \ No newline at end of file +} diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollState.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollState.kt index 0fb16459d8..393ab9fa4c 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollState.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollState.kt @@ -1,3 +1,26 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.business.poll.filter import org.projectforge.common.i18n.I18nEnum @@ -7,4 +30,4 @@ enum class PollState(val key: String) : I18nEnum { override val i18nKey: String get() = "poll.$key" -} \ No newline at end of file +} diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollStateFilter.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollStateFilter.kt index b813c71b99..6446987a1f 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollStateFilter.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollStateFilter.kt @@ -1,3 +1,26 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.business.poll.filter; import org.projectforge.business.poll.PollDO diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index eeb54b0592..11ae7365ee 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -94,188 +94,6 @@ upload=Upload uptodate=up-to-date wizard=Wizard # Common -akquise=Acquisition -changes=Changes -charactersLeft=characters left. -color.error.unknownFormat=Color code in unsupported format. Supported hex formats: #abc or #abcdef -comment=Comment -completed=completed -contactPerson=Contact person -created=created -createdBy=created by -date=date -date.begin=Start date -date.end=End date -date.from=from -date.until=until -dateFormat=Date format -dateFormat.xls=Excel date format -day=day(s) -days=days -deadline=Deadline -default=Default -deleted=deleted -description=Description -dueDate=Due date -edit=Edit -email=E-mail -ended=ended -error=An error occurs: {0} -errors=Errors -export=Export -fieldNotHistorizable=Field not historizable. -file=file -file.upload.audit.action.delete=deleted -file.upload.audit.action.download=downloaded -file.upload.audit.action.downloadAll=downloaded all -file.upload.audit.action.downloadMulti=downloaded multi -file.upload.audit.action.modification=modified (name or description) -file.upload.audit.action.upload=uploaded -file.upload.choose=Choose file -file.upload.deleteSelected=Delete selected -file.upload.deleteSelected.confirm=Do you really want to delete all selected files? -file.upload.downloadSelected=Download selected -file.upload.dropArea=Select a file, or drop it here. -file.upload.error.fileAlreadyExists=File ''{0}'' already exists. -file.upload.error.maxSizeExceeded=Can''t upload file ''{0}'', maximum configured file size is exceeded: {1}>{2}. -file.upload.error.maxSizeOfExceeded=File upload rejected: maximum size of {0} exceeded. -file.upload.error.noFileSelected=No file selected -file.upload.error.toManyFiles=You tried to upload to many files at once. -filing=filing -filter=filter -filter.all=all -filter.extendedSearch=Extended search -filter.faulty=faulty -filter.newest=newest -financeAdministration=Finance administration -firstName=First name -gender=Gender -gender.diverse=diverse -gender.female=female -gender.male=male -gender.notApplicable=not applicable -gender.notKnown=not known -gender.unknown=unknown -group=Group -holidays=Holidays -hours=Hours -id=Id -imageFile=Image -inherit=inherit -key=Key -language=Language -lastUpdate=last modification -legend=Legend -list=List -listView=List view -loading=Loading... -loggedInUserInfo=Current user: -longFormat=Long format -misc=Miscellaneous -modifications=Modifications -modificationTime=Time of modification -modified=modified -modifiedBy=modified by -modifiedHistoryValue=history value of modification -moreEntriesAvailable=More entries available. -name=Name -new=New -nickname=Nickname -no=No -notEnded=not ended -nothingFound=Nothing found. -notLoggedIn=Not logged in. -notVisible=not visible -oclock=o'clock -onlyDeleted=only deleted -onlyDeleted.tooltip=Only deleted entries will be displayed (in general independent of any other filter settings). -operation.deleted=deleted -operation.inserted=inserted -operation.markAsDeleted=marked as deleted -operation.undefined=undefined -operation.undeleted=undeleted -operation.updated=updated -organization=Organization -password=Password -passwordRepeat=Repeat password -percent=Percent -pleaseChoose=Please choose -printView=print view -priority=Priority -priority.high=high -priority.highest=highest -priority.least=least -priority.low=low -priority.middle=middle -priority.without=without -quickselect=Quick-select -recursive=recursive -rest=Rest -resubmissionOnDate=Resubmission on -resumption=Resumption -searchFilter=Search filter -searchString=Search string -securityAdvice=Security advice -settings=Settings -shortDescription=Short description -sql=SQL -statistics=Statistics -status=Status -sum=Sum -task=Structure element -templates=Templates -templates.new=New template -text=Text -timeago.afewseconds=a few seconds ago -timeago.days={0} days ago -timeago.days.one=yesterday -timeago.hours={0} hours ago -timeago.hours.one=an hour ago -timeago.minutes={0} minutes ago -timeago.minutes.one=a minute ago -timeago.months={0} months ago -timeago.months.one=a month ago -timeago.negative=in the future! -timeago.seconds={0} seconds ago -timeago.weeks={0} weeks ago -timeago.weeks.one=a week ago -timeago.years={0} years ago -timeago.years.one=a year ago -timeleft.afewseconds=in a few seconds -timeleft.days=in {0} days -timeleft.days.one=tomorrow -timeleft.hours=in {0} hours -timeleft.hours.one=in an hour -timeleft.minutes=in {0} minutes -timeleft.minutes.one=in a minute -timeleft.months=in {0} months -timeleft.months.one=in a month -timeleft.negative=in the past! -timeleft.seconds=in {0} seconds -timeleft.weeks=in {0} weeks -timeleft.weeks.one=in a week -timeleft.years=in {0} years -timeleft.years.one=in a year -timeNotation=Time notation -timeNotation.12=12-hour notation -timeNotation.24=24-hour notation -timeOfCreation=Time of creation -timeOfLastUpdate=Time of last update -timePeriod=Time period -timestamp=Time stamp -timezone=Time zone -tip=Tip -title=Title -totalSum=Total sum -undefined=undefined -until=until -untitled=untitled -user=User -username=User name -value=Value -values=Values -weekOfYear=Week of year -yes=Yes access=Accessright access.accessTable=Access table access.exception.demoUserHasNoAccess=The demo user is blocked for this action. @@ -577,6 +395,7 @@ administration.setup.target.testdata.tooltip=Database with test data (for test s administration.setup.title=First login - set-up ProjectForge administration.title=Administration agGrid.sortInfo=You may sort multiple columns by pressing shift-key while clicking column heads. You may also re-order columns and change the width of the columns. +akquise=Acquisition attachment=Attachment attachment.checksum=Checksum attachment.encrypt=Encrypt @@ -773,6 +592,10 @@ calendar.view.workDays=Workdays calendar.week=week calendar.weekOfYearShortLabel=CW calendar.year=Year +changes=Changes +charactersLeft=characters left. +color.error.unknownFormat=Color code in unsupported format. Supported hex formats: #abc or #abcdef +comment=Comment common.attention=Attention! common.customized=Customized common.import.action.commit=Commit @@ -817,6 +640,7 @@ common.resultholder.ok=ok common.resultholder.warning=warning common.uploadpanel.filetolarge=Selected file is too large. The maximal size is {0}. common.uploadpanel.filewrongtype=Selected file is in incorrect format. Supported formats: {0} +completed=completed contact.birthday=Birthday contact.contacts=Addresses contact.emailValues=Email addresses @@ -835,13 +659,30 @@ contact.type.other=other contact.type.own=own contact.type.postal=postal contact.type.private=private +contactPerson=Contact person contextMenu.cancel=Cancel contextMenu.newTab=Open in new tab +created=created +createdBy=created by +date=date +date.begin=Start date +date.end=End date +date.from=from +date.until=until +dateFormat=Date format +dateFormat.xls=Excel date format +day=day(s) +days=days +deadline=Deadline +default=Default +deleted=deleted +description=Description dialog.title.error=An error occured! dialog.title.information=Information dialog.title.message=Message dialog.title.success=Success message dialog.title.warning=Warning +dueDate=Due date duration.days={0} days duration.days.one=1 day duration.hours={0} hours @@ -851,6 +692,10 @@ duration.minutes.one=1 minute duration.seconds={0} seconds duration.seconds.one=1 second dvelop.title=D-velop +edit=Edit +email=E-mail +ended=ended +error=An error occurs: {0} error.date.yearOutOfRange=The year of the date is out of range: ${minimumYear} - ${maximumYear}. error.dateInFuture=Date is in future. error.endDateBeforeBeginDate=End date can't be before begin date. @@ -865,12 +710,14 @@ errorpage.feedback.messageNumber=Message number errorpage.feedback.placeholder=Please decripe how you get the failure. errorpage.title=An error has been occurred. errorpage.unknownError=An internal error has been occurred. +errors=Errors exception.constraintViolation=A constraint violation occured. The current data object results in a conflict with another existing (perhaps deleted) object in the data base. exception.flowscope.notExists=Flow scope does not exist (maybe caused by a timeout). Please try again. exception.internalError=An internal error occured. A log message was produced. exception.luceneParseError=Error while parsing string: {0}. exception.pleaseContactDeveloperTeam=Internal error, please contact the developer team (the message id in error log: #{0}). exception.scriptError=Exception while script execution thrown: {0} +export=Export feedback.link.tooltip=Send feed-back feedback.mailSendSuccessful=E-Mail was sent successful. Thank you for your feed-back! feedback.receiver=Receiver @@ -1289,8 +1136,32 @@ fibu.tooltip.unselectKost1=Unselect Kost1 fibu.tooltip.unselectKost2=Unselect Kost2 fibu.tooltip.unselectKunde=Unselect customer fibu.tooltip.unselectProjekt=Unselect project +fieldNotHistorizable=Field not historizable. +file=file file.panel.deleteExistingFile.heading=Deletion of the file? file.panel.deleteExistingFile.question=Do you really want to delete or overwrite the existing file? +file.upload.audit.action.delete=deleted +file.upload.audit.action.download=downloaded +file.upload.audit.action.downloadAll=downloaded all +file.upload.audit.action.downloadMulti=downloaded multi +file.upload.audit.action.modification=modified (name or description) +file.upload.audit.action.upload=uploaded +file.upload.choose=Choose file +file.upload.deleteSelected=Delete selected +file.upload.deleteSelected.confirm=Do you really want to delete all selected files? +file.upload.downloadSelected=Download selected +file.upload.dropArea=Select a file, or drop it here. +file.upload.error.fileAlreadyExists=File ''{0}'' already exists. +file.upload.error.maxSizeExceeded=Can''t upload file ''{0}'', maximum configured file size is exceeded: {1}>{2}. +file.upload.error.maxSizeOfExceeded=File upload rejected: maximum size of {0} exceeded. +file.upload.error.noFileSelected=No file selected +file.upload.error.toManyFiles=You tried to upload to many files at once. +filing=filing +filter=filter +filter.all=all +filter.extendedSearch=Extended search +filter.faulty=faulty +filter.newest=newest finance.accountingRecord.dc=DC finance.accountingRecord.dc.credit=credit finance.accountingRecord.dc.debit=debit @@ -1298,6 +1169,8 @@ finance.datev.import.error.titleRowMissed=Title row missed. finance.datev.upload.hint=*.xls; max. {0} finance.datev.uploadAccountingRecords=upload accounting records finance.datev.uploadAccountList=upload accounts +financeAdministration=Finance administration +firstName=First name form.ajaxEditableLabel.tooltip=Click for edit mode. gantt.access.all=All gantt.access.owner=Owner @@ -1366,6 +1239,14 @@ gantt.x.unit.day=day gantt.x.unit.month=month gantt.x.unit.quarter=quarter gantt.x.unit.week=week +gender=Gender +gender.diverse=diverse +gender.female=female +gender.male=male +gender.notApplicable=not applicable +gender.notKnown=not known +gender.unknown=unknown +group=Group group.assignedUsers=Assigned users group.error.groupnameAlreadyExists=Group name already exists. group.groups=Groups @@ -1392,6 +1273,8 @@ history.newValue=New value history.oldValue=Old value history.propertyName=Property history.was=was +holidays=Holidays +hours=Hours hr.planning.description=Activity report hr.planning.entry.copyFromPredecessor=Copy from predecessor hr.planning.entry.error.entryDoesAlreadyExistForUserAndWeekOfYear=An entry with the same user and week of year does already exist. @@ -1427,6 +1310,8 @@ hr.planning.unassignedHours=without assigned day hr.planning.view.title=Human resources view hr.planning.weekend=Week-end hr.planning.workdays=Workdays +id=Id +imageFile=Image import.confirmMessage=Would you like to import the selected entries now? This option isn't undoable. import.display.options=Display options import.entry.error=Error @@ -1461,6 +1346,7 @@ import.title=Import tool index.development=Website for developers index.website=Website index.welcome=Welcome to ProjectForge. +inherit=inherit jira.chooseProject=--- Choose JIRA project --- jobs.error.refusedByAnotherRunningJob=Job couldn't be started: Another job is already running in this queue. jobs.error.waitingTimeExceeded=Job couldn't be started due to a time-out: This job is blocked by other long-running job(s). @@ -1479,6 +1365,7 @@ jobs.job.terminatedAt=Terminated at jobs.job.title=Job title jobs.monitor.noJobsAvailable=No current jobs jobs.monitor.title=Job monitor +key=Key label.data=Data label.exportFormat=Export format label.filterSettings=Filter settings @@ -1496,6 +1383,8 @@ label.script=Script label.script.result=Script result label.sendEMailNotification=Send an e-mail notification? label.sendShortMessage=Send the assignee a text message? +language=Language +lastUpdate=last modification ldap=LDAP ldap.gidNumber=GID number ldap.gidNumber.alreadyInUse=GID number is already assigned to another group. The next free GID number is {0}. @@ -1546,7 +1435,11 @@ legalAffaires.contract.type=Type legalAffaires.contract.validity=Validity legalAffaires.contract.validity.from=Validity from legalAffaires.contract.validity.until=Validity until +legend=Legend license.upload.title=License file +list=List +listView=List view +loading=Loading... locale.de=German locale.en=English locale.zh=Chinese @@ -1555,6 +1448,7 @@ log.level.error=Error log.level.info=Info log.level.trace=Trace log.level.warn=Warn +loggedInUserInfo=Current user: login.adminLoginRequired=Maintenance mode: Please use ProjectForge only as administrator! login.error.loginExpired=Login failed. Please contact an administrator because your log-in is expired. login.error.loginFailed=Login failed. Please check your login name and password (case sensitive!). @@ -1571,6 +1465,7 @@ login.successful=You're sucessfully logged in. Enjoy ProjectForge! login.timeOffset=Your account is now locked for further {0} seconds due to {1} failed login attempts. Please try again later. login.title=Login logout.successful=Logout was successful. +longFormat=Long format mail.error.exception=An error occured during while sending e-mail. A log message was produced. Please contact a system administrator. mail.error.missingToAddress=Sending of e-mail failed, no receiver address is given. mail.template.closing=Enjoy your work with ProjectForge! @@ -1709,11 +1604,35 @@ message.successfullChanged=Successfull changed. message.successfullCompleted=Successfull completed: {0} message.title=Message message.wicket.pageExpired=The demanded page is expired. +misc=Miscellaneous +modifications=Modifications +modificationTime=Time of modification +modified=modified +modifiedBy=modified by +modifiedHistoryValue=history value of modification +moreEntriesAvailable=More entries available. multiselection.aggrid.selection.info.message=\ * You may use the shift-key for multi-selection on mouse clicks.\n\ * You may use cursor up- and down keys to navigate and use space to (de)select rows. multiselection.aggrid.selection.info.title=Multi selection of rows multiselection.button=Multi selection +name=Name +new=New +nickname=Nickname +no=No +notEnded=not ended +nothingFound=Nothing found. +notLoggedIn=Not logged in. +notVisible=not visible +oclock=o'clock +onlyDeleted=only deleted +onlyDeleted.tooltip=Only deleted entries will be displayed (in general independent of any other filter settings). +operation.deleted=deleted +operation.inserted=inserted +operation.markAsDeleted=marked as deleted +operation.undefined=undefined +operation.undeleted=undeleted +operation.updated=updated orga.post.inhalt=Content orga.post.type=Type orga.post.type.brief=Letter @@ -1756,10 +1675,12 @@ orga.visitorbook.visitortype.family=Family orga.visitorbook.visitortype.normal=Normal orga.visitorbook.visitortype.supplier=Supplier orga.visitorbook.visitortype.workman=Workman +organization=Organization pacman.title=Pacman panel.error.customernameNotFound=Customer name doesn't exist. panel.error.groupNotFound=Group doesn't exist. panel.error.projectNotFound=Project doesn't exist. +password=Password password.forgotten.link=Forgotten credentials? password.forgotten.mail.subject=Password reset ProjectForge® password.forgotten.mailSentTo=Mail with reset link mailed to ''{0}''. Please check also Your spam folder and check, if Your username or e-mail is correct. @@ -1771,10 +1692,13 @@ password.reset.mail.message.1=Please note: You will need a second authentificati password.reset.mail.message.2=If you haven't configured a second factor, such as Authenticator-App or mobile phone, You have to contact Your ProjectForge administrator. password.reset.title=Reset password password.reset.username_email=Username/e-mail +passwordRepeat=Repeat password +percent=Percent personal.statistics.timesheetDisciplineChart.title=Early booking of time sheets is essentiell for a successful project management! personal.statistics.timesheetDisciplineChart1.legend=The last {0} days results in {1} workhours. For this period {2} hours where already booked. personal.statistics.timesheetDisciplineChart2.legend=In the last {0} days time sheets were booked after {2} days (average). The goal is to achieve {1} days at maximum. personal.statistics.title=My Statistics +pleaseChoose=Please choose plugins.teamcal.access=Access right plugins.teamcal.access.groups=Groups plugins.teamcal.access.title=Access options @@ -1967,62 +1891,118 @@ plugins.teamcal.title.add=Add calendar plugins.teamcal.title.edit=Edit team calendar plugins.teamcal.title.heading=Calendar plugins.teamcal.title.list=List of calendars +printView=print view +priority=Priority +priority.high=high +priority.highest=highest +priority.least=least +priority.low=low +priority.middle=middle +priority.without=without +quickselect=Quick-select +recursive=recursive +rest=Rest +resubmissionOnDate=Resubmission on +resumption=Resumption +searchFilter=Search filter +searchString=Search string +securityAdvice=Security advice +settings=Settings +shortDescription=Short description +sql=SQL +statistics=Statistics +status=Status +sum=Sum +task=Structure element +templates=Templates +templates.new=New template +text=Text +timeago.afewseconds=a few seconds ago +timeago.days={0} days ago +timeago.days.one=yesterday +timeago.hours={0} hours ago +timeago.hours.one=an hour ago +timeago.minutes={0} minutes ago +timeago.minutes.one=a minute ago +timeago.months={0} months ago +timeago.months.one=a month ago +timeago.negative=in the future! +timeago.seconds={0} seconds ago +timeago.weeks={0} weeks ago +timeago.weeks.one=a week ago +timeago.years={0} years ago +timeago.years.one=a year ago +timeleft.afewseconds=in a few seconds +timeleft.days=in {0} days +timeleft.days.one=tomorrow +timeleft.hours=in {0} hours +timeleft.hours.one=in an hour +timeleft.minutes=in {0} minutes +timeleft.minutes.one=in a minute +timeleft.months=in {0} months +timeleft.months.one=in a month +timeleft.negative=in the past! +timeleft.seconds=in {0} seconds +timeleft.weeks=in {0} weeks +timeleft.weeks.one=in a week +timeleft.years=in {0} years +timeleft.years.one=in a year +timeNotation=Time notation +timeNotation.12=12-hour notation +timeNotation.24=24-hour notation +timeOfCreation=Time of creation +timeOfLastUpdate=Time of last update +timePeriod=Time period +timestamp=Time stamp +timezone=Time zone +tip=Tip +title=Title +totalSum=Total sum +undefined=undefined +until=until +untitled=untitled +user=User +username=User name +value=Value +values=Values +weekOfYear=Week of year +yes=Yes # poll plugin poll=Poll -poll.title=Title -poll.title.list=Polls -poll.title.add=Create new Poll -poll.title.edit=Edit Poll -poll.owner=Owner -poll.description=Description -poll.attendees=Attendees -poll.groupAttendees=Attendee Groups -poll.fullAccessUsers=Full Access User -poll.fullAccessGroups=Full Access Groups -poll.date=Date -poll.deadline=Deadline -poll.location=Location -poll.state=State -poll.assignment=Assignment poll.access=Access -poll.attendee=Attendee -poll.other=Other -poll.running=Running -poll.finished=Finished -poll.infopage=Info Page -poll.guide=Poll Guide -poll.question=Question -poll.question.textQuestion=Text Question -poll.questionType=Question Type poll.answer=Answer -poll.yourAnswers=Your answers -poll.delegationAnswers=Answers of +poll.assignment=Assignment +poll.attendee=Attendee +poll.attendees=Attendees poll.button.addQuestion=Add own question -poll.button.micromataTemplate=Use Micromata Template poll.button.finish=Finish Poll -poll.confirmation.finish=Do you really want to finish this Poll? +poll.button.micromataTemplate=Use Micromata Template poll.confirmation.creation=Do you really want to create the poll? You won't be able to edit the questions afterwards.\ Also make sure to add attendees for your poll. poll.confirmation.deleteAnswer=Do you really want to delete this answer? poll.confirmation.deleteQuestion=Do you really want to delete this question? -poll.popup.closed=The poll was already closed. +poll.confirmation.finish=Do you really want to finish this Poll? +poll.date=Date +poll.deadline=Deadline +poll.delegationAnswers=Answers of +poll.description=Description poll.error.oneQuestionRequired=At least one question is required. +poll.exception.noAttendee=This user is not part of the poll. poll.export.response.poll=Export results -poll.respond=Send responses -poll.response.page=Poll Response Page -poll.response.title=Poll Response Page -poll.response.mail.update.subject=Response was edited by {0} -poll.response.mail.update.content=

Dear {0},

\ -

I wanted to inform you that Person {2} has updated their answer to Poll {1}.

\ -

If you were not notified about this,

\ -

I recommend reaching out to the person directly or adjusting your results accordingly.

\ -

You can modify your response until "{3}".

\ +poll.finished=Finished +poll.fullAccessGroups=Full Access Groups +poll.fullAccessUsers=Full Access User +poll.groupAttendees=Attendee Groups +poll.guide=Poll Guide +poll.infopage=Info Page +poll.location=Location +poll.mail.ended.content=

Dear Attendees,

\ +

We wanted to let you know that the poll "{0}" created by {1} has now ended. Thank you to everyone who participated and provided valuable input.

\ +

If you missed the deadline to submit your responses, we encourage you to still share your thoughts with us. While we may not be able to include your responses in the official results, your feedback is still valuable for future polls.

\ +

Thank you again for your participation.

\

Best regards,

\ -

{2}

-poll.userDelegation=User delegation -poll.selectUser=Select user -poll.exception.noAttendee=This user is not part of the poll. -poll.mail.endingSoon.subject=Poll ending in {0} days +

{1}

+poll.mail.ended.subject=Poll ended poll.mail.endingSoon.content=

Dear Attendees,

\

This is a friendly reminder that the poll "{0}" created by {1} is ending soon, on {2}. Please make sure to submit your responses before the deadline.

\

If you have not yet had a chance to participate, please take a few moments to do so before the poll closes. Your input is important and valued.

\ @@ -2030,20 +2010,40 @@ poll.mail.endingSoon.content=

Dear Attendees,

\

Thank you for your attention, and have a great day!

\

Best regards,

\

{1}

-poll.mail.ended.subject=Poll ended -poll.mail.ended.content=

Dear Attendees,

\ -

We wanted to let you know that the poll "{0}" created by {1} has now ended. Thank you to everyone who participated and provided valuable input.

\ -

If you missed the deadline to submit your responses, we encourage you to still share your thoughts with us. While we may not be able to include your responses in the official results, your feedback is still valuable for future polls.

\ -

Thank you again for your participation.

\ -

Best regards,

\ -

{1}

-poll.mail.update.subject=Poll was edited +poll.mail.endingSoon.subject=Poll ending in {0} days poll.mail.update.content=

Dear Attendees,

\

We wanted to let you know that the poll "{0}" was edited recently.

\

If you already submitted your answers, you should check, if there were any major changes made.

\

Thank you again for your participation.

\

Best regards,

\

{1}

+poll.mail.update.subject=Poll was edited +poll.other=Other +poll.owner=Owner +poll.popup.closed=The poll was already closed. +poll.question=Question +poll.question.textQuestion=Text Question +poll.questionType=Question Type +poll.respond=Send responses +poll.response.mail.update.content=

Dear {0},

\ +

I wanted to inform you that Person {2} has updated their answer to Poll {1}.

\ +

If you were not notified about this,

\ +

I recommend reaching out to the person directly or adjusting your results accordingly.

\ +

You can modify your response until "{3}".

\ +

Best regards,

\ +

{2}

+poll.response.mail.update.subject=Response was edited by {0} +poll.response.page=Poll Response Page +poll.response.title=Poll Response Page +poll.running=Running +poll.selectUser=Select user +poll.state=State +poll.title=Title +poll.title.add=Create new Poll +poll.title.edit=Edit Poll +poll.title.list=Polls +poll.userDelegation=User delegation +poll.yourAnswers=Your answers projectmanagement.personDays=Person days projectmanagement.personDays.short=pd question.deleteQuestion=Do your really want to delete this object finally? diff --git a/projectforge-business/src/main/resources/I18nResources_de.properties b/projectforge-business/src/main/resources/I18nResources_de.properties index 53ab07709b..75684f23df 100644 --- a/projectforge-business/src/main/resources/I18nResources_de.properties +++ b/projectforge-business/src/main/resources/I18nResources_de.properties @@ -5,6 +5,11 @@ # This main function sorts all entries in default properties and ensures the same output in this lang properties. # # Any comment or blank line of this file will be ignored and replaced by such lines from default properties. + +# ******** Entries in 'I18nResources_de.properties' MISSED in default 'I18nResources.properties': +# +poll.error.closed=Umfrage wurde bereits beendet + # Missed translations from default 'I18nResources.properties' (might be OK): # # exception.notYetSupported=Not yet supported. @@ -36,6 +41,8 @@ # menu.luceneConsole=Lucene Console # menu.multiTenancy=Multi tenancy # menu.pluginAdmin=Plugins +# poll.response.mail.update.content=

Dear {0},

... (multiline) +# poll.response.mail.update.subject=Response was edited by {0} # sipgate.title=Sipgate # system.admin.alertMessage.copyAndPaste.title=For copy & paste # system.admin.button.checkI18nProperties=Check i18n properties @@ -87,6 +94,8 @@ # system.pluginAdmin.button.deactivate=Deactivate # system.pluginAdmin.title=Plugins # system.statistics.databasePool=Data base pool + + # Wicket: datatable.no-records-found=Keine Einträge gefunden. NavigatorLabel=Zeile ${from} bis ${to} von ${of}. @@ -183,188 +192,6 @@ upload=Hochladen uptodate=aktuell wizard=Assistent # Common -akquise=Akquise -changes=Änderungen -charactersLeft=Zeichen übrig. -color.error.unknownFormat=Farbcode kann nicht verarbeitet werden. Unterstützte Hex-Farbformate: #abc or #abcdef -comment=Bemerkung -completed=erledigt -contactPerson=Ansprechpartner:in -created=angelegt -createdBy=angelegt von -date=Datum -date.begin=Beginndatum -date.end=Endedatum -date.from=von -date.until=bis -dateFormat=Datumsformat -dateFormat.xls=Exceldatumsformat -day=Tag(e) -days=Tage -deadline=Frist -default=Standard -deleted=gelöscht -description=Beschreibung -dueDate=Fälligkeit -edit=Bearbeiten -email=E-Mail -ended=beendet -error=Ein Fehler trat auf: {0} -errors=Fehler -export=Export -fieldNotHistorizable=Dieses Feld wird nicht historisiert! -file=Datei -file.upload.audit.action.delete=gelöscht -file.upload.audit.action.download=heruntergeladen -file.upload.audit.action.downloadAll=alles heruntergeladen -file.upload.audit.action.downloadMulti=mehrere heruntergeladen -file.upload.audit.action.modification=geändert (Dateiname oder Beschreibung) -file.upload.audit.action.upload=hochgeladen -file.upload.choose=Datei wählen -file.upload.deleteSelected=Ausgewählte Dateien löschen -file.upload.deleteSelected.confirm=Sollen jetzt wirklich alle ausgewählten Dateien unwiderruflich gelöscht werden? -file.upload.downloadSelected=Ausgewählte Dateien herunterladen -file.upload.dropArea=Datei auswählen oder hier hinziehen. -file.upload.error.fileAlreadyExists=Die Datei ''{0}'' existiert bereits. -file.upload.error.maxSizeExceeded=Datei ''{0}'' kann nicht hochgeladen werden. Die konfigurierte Maximalgröße wurde überschritten: {1}>{2}. -file.upload.error.maxSizeOfExceeded=Die Datei konnte nicht hochgeladen werden. Die Maximalgröße von {0} wurde überschritten. -file.upload.error.noFileSelected=Keine Datei gewählt -file.upload.error.toManyFiles=Es wurden zu viele Dateien für das gleichzeitige Hochladen ausgewählt. -filing=Ablageort -filter=Filter -filter.all=alle -filter.extendedSearch=Erweiterte Suche -filter.faulty=fehlerhaft -filter.newest=die neuesten -financeAdministration=Finanzbuchhaltung -firstName=Vorname -gender=Geschlecht -gender.diverse=divers -gender.female=weiblich -gender.male=männlich -gender.notApplicable=nicht zutreffend -gender.notKnown=unbekannt -gender.unknown=unbekannt -group=Gruppe -holidays=Feiertage -hours=Stunden -id=Id -imageFile=Bild -inherit=vererbt -key=Schlüssel -language=Sprache -lastUpdate=letzte Änderung -legend=Legende -list=Liste -listView=Listenansicht -loading=Laden... -loggedInUserInfo=Angemeldet als: -longFormat=Langes Format -misc=Verschiedenes -modifications=Änderungen -modificationTime=Änderungszeitraum -modified=geändert -modifiedBy=geändert durch -modifiedHistoryValue=Wert in der Änderungshistorie -moreEntriesAvailable=Mehr Einträge vorhanden. -name=Name -new=Neu -nickname=Rufname -no=Nein -notEnded=nicht beendet -nothingFound=Nichts gefunden. -notLoggedIn=Nicht angemeldet -notVisible=nicht sichtbar -oclock=Uhr -onlyDeleted=nur gelöschte -onlyDeleted.tooltip=Es werden nur gelöschte Datensätze angezeigt (i. d. R. unabhängig von anderen Filterangaben). -operation.deleted=gelöscht -operation.inserted=angelegt -operation.markAsDeleted=als gelöscht markiert -operation.undefined=undefiniert -operation.undeleted=wiederhergestellt -operation.updated=geändert -organization=Firma -password=Passwort -passwordRepeat=Passwort wiederholen -percent=Prozent -pleaseChoose=Bitte wählen -printView=Druckansicht -priority=Priorität -priority.high=hoch -priority.highest=sehr hoch -priority.least=gering -priority.low=niedrig -priority.middle=mittel -priority.without=ohne -quickselect=Schnellauswahl -recursive=rekursiv -rest=Rest -resubmissionOnDate=Wiedervorlage -resumption=Wiedervorlage -searchFilter=Suchfilter -searchString=Suchtext -securityAdvice=Sicherheitshinweis -settings=Einstellungen -shortDescription=Kurzbeschreibung -sql=SQL -statistics=Statistik -status=Status -sum=Summe -task=Strukturelement -templates=Vorlagen -templates.new=Neue Vorlage -text=Text -timeago.afewseconds=vor einigen Sekunden -timeago.days=vor {0} Tagen -timeago.days.one=gestern -timeago.hours=vor {0} Stunden -timeago.hours.one=vor einer Stunde -timeago.minutes=vor {0} Minuten -timeago.minutes.one=vor einer Minute -timeago.months=vor {0} Monaten -timeago.months.one=vor einem Monat -timeago.negative=in der Zukunft! -timeago.seconds=vor {0} Sekunden -timeago.weeks=vor {0} Wochen -timeago.weeks.one=vor einer Woche -timeago.years=vor {0} Jahren -timeago.years.one=vor einem Jahr -timeleft.afewseconds=in einigen Sekunden -timeleft.days=in {0} Tagen -timeleft.days.one=in einem Tag -timeleft.hours=in {0} Stunden -timeleft.hours.one=in einer Stunde -timeleft.minutes=in {0} Minuten -timeleft.minutes.one=in einer Minute -timeleft.months=in {0} Monaten -timeleft.months.one=in einem Monat -timeleft.negative=in der Vergangenheit! -timeleft.seconds=in {0} Sekunden -timeleft.weeks=in {0} Wochen -timeleft.weeks.one=in einer Woche -timeleft.years=in {0} Jahren -timeleft.years.one=in einem Jahr -timeNotation=Uhrzeitformat -timeNotation.12=12 Stunden -timeNotation.24=24 Stunden -timeOfCreation=Anlagezeitpunkt -timeOfLastUpdate=Zeitpunkt der letzten Änderung -timePeriod=Zeitraum -timestamp=Zeitstempel -timezone=Zeitzone -tip=Tipp -title=Titel -totalSum=Gesamtsumme -undefined=undefiniert -until=bis -untitled=unbenannt -user=Benutzer:in -username=Benutzer:innenname -value=Wert -values=Werte -weekOfYear=Kalenderwoche -yes=Ja access=Zugriffsrecht access.accessTable=Zugriffstabelle access.exception.demoUserHasNoAccess=Der oder die Demobenutzer:in ist für diese Aktion gesperrt. @@ -666,6 +493,7 @@ administration.setup.target.testdata.tooltip=Datenbank mit Testdaten (für Tests administration.setup.title=Erstanmeldung - ProjectForge einrichten administration.title=Administration agGrid.sortInfo=Es können mehrere Spalten sortiert werden, indem die Hochstelltaste beim Anklicken von weiteren Spaltenköpfen gedrückt gehalten wird. Außerdem können Spalten umsortiert und in der Breite verändert werden. +akquise=Akquise attachment=Anhang attachment.checksum=Prüfsumme attachment.encrypt=Verschlüsseln @@ -862,6 +690,10 @@ calendar.view.workDays=Werktage calendar.week=Woche calendar.weekOfYearShortLabel=KW calendar.year=Jahr +changes=Änderungen +charactersLeft=Zeichen übrig. +color.error.unknownFormat=Farbcode kann nicht verarbeitet werden. Unterstützte Hex-Farbformate: #abc or #abcdef +comment=Bemerkung common.attention=Achtung! common.customized=Angepasst common.import.action.commit=Ãœbernehmen @@ -906,6 +738,7 @@ common.resultholder.ok=OK common.resultholder.warning=Warnung common.uploadpanel.filetolarge=Die ausgewählte Datei ist zu groß. Die maximale Größe beträgt {0}. common.uploadpanel.filewrongtype=Die ausgewählte Datei besitzt ein nicht unterstütztes Dateiformat. Unterstützte Formate: {0}. +completed=erledigt contact.birthday=Geburtstag contact.contacts=Adressen contact.emailValues=E-Mailadressen @@ -924,13 +757,30 @@ contact.type.other=andere contact.type.own=eigene contact.type.postal=postalisch contact.type.private=privat +contactPerson=Ansprechpartner:in contextMenu.cancel=Abbrechen contextMenu.newTab=Link in neuem Tab öffnen +created=angelegt +createdBy=angelegt von +date=Datum +date.begin=Beginndatum +date.end=Endedatum +date.from=von +date.until=bis +dateFormat=Datumsformat +dateFormat.xls=Exceldatumsformat +day=Tag(e) +days=Tage +deadline=Frist +default=Standard +deleted=gelöscht +description=Beschreibung dialog.title.error=Es ist ein Fehler aufgetreten! dialog.title.information=Information dialog.title.message=Meldung dialog.title.success=Erfolgsmeldung dialog.title.warning=Warnung +dueDate=Fälligkeit duration.days={0} Tage duration.days.one=1 Tag duration.hours={0} Stunden @@ -940,6 +790,10 @@ duration.minutes.one=1 Minute duration.seconds={0} Sekunden duration.seconds.one=1 Sekunde ### not translated: dvelop.title=D-velop +edit=Bearbeiten +email=E-Mail +ended=beendet +error=Ein Fehler trat auf: {0} error.date.yearOutOfRange=Das Jahr liegt außerhalb des zulässigen Bereichs: ${minimumYear} - ${maximumYear}. error.dateInFuture=Datum darf nicht in der Zukunft liegen. error.endDateBeforeBeginDate=Das Endedatum darf nicht vor dem Anfangsdatum liegen. @@ -954,12 +808,14 @@ errorpage.feedback.messageNumber=Fehlernummer errorpage.feedback.placeholder=Bitte hier beschreiben, wie es zum Fehlverhalten kam. errorpage.title=Ein Fehler ist aufgetreten. errorpage.unknownError=Ein interner Fehler ist aufgetreten. +errors=Fehler exception.constraintViolation=Es trat eine Verletzung der Datenintegrität in der Datenbank auf. Dies kann daran liegen, dass z. B. der aktuelle Datensatz mit einem anderen (ggf. auch gelöschten) kollidiert. exception.flowscope.notExists=Flow existiert nicht (vielleicht auf Grund einer Zeitüberschreitung). Bitte nochmals versuchen. exception.internalError=Es ist ein interner Fehler aufgetreten. Dieser wurde protokolliert. exception.luceneParseError=Fehler bei der Vearbeitung des Suchtextes: {0} exception.pleaseContactDeveloperTeam=Interner Fehler, bitte die Entwickler:innen informieren. Die Fehlerkennung in den Fehlerprotokollen lautet #{0}. exception.scriptError=Fehler bei der Script-Ausführung\: {0} +export=Export feedback.link.tooltip=Feedback senden feedback.mailSendSuccessful=Eine E-Mail wurde erfolgreich versendet. Vielen Dank für die Mithilfe\! feedback.receiver=Empfänger:in @@ -1378,8 +1234,32 @@ fibu.tooltip.unselectKost1=Kost1-Auswahl aufheben fibu.tooltip.unselectKost2=Kost2-Auswahl aufheben fibu.tooltip.unselectKunde=Kundenauswahl aufheben fibu.tooltip.unselectProjekt=Projektauswahl aufheben +fieldNotHistorizable=Dieses Feld wird nicht historisiert! +file=Datei file.panel.deleteExistingFile.heading=Datei wirklich löschen? file.panel.deleteExistingFile.question=Soll die vorhandene Datei wirklich gelöscht bzw. überschrieben werden? +file.upload.audit.action.delete=gelöscht +file.upload.audit.action.download=heruntergeladen +file.upload.audit.action.downloadAll=alles heruntergeladen +file.upload.audit.action.downloadMulti=mehrere heruntergeladen +file.upload.audit.action.modification=geändert (Dateiname oder Beschreibung) +file.upload.audit.action.upload=hochgeladen +file.upload.choose=Datei wählen +file.upload.deleteSelected=Ausgewählte Dateien löschen +file.upload.deleteSelected.confirm=Sollen jetzt wirklich alle ausgewählten Dateien unwiderruflich gelöscht werden? +file.upload.downloadSelected=Ausgewählte Dateien herunterladen +file.upload.dropArea=Datei auswählen oder hier hinziehen. +file.upload.error.fileAlreadyExists=Die Datei ''{0}'' existiert bereits. +file.upload.error.maxSizeExceeded=Datei ''{0}'' kann nicht hochgeladen werden. Die konfigurierte Maximalgröße wurde überschritten: {1}>{2}. +file.upload.error.maxSizeOfExceeded=Die Datei konnte nicht hochgeladen werden. Die Maximalgröße von {0} wurde überschritten. +file.upload.error.noFileSelected=Keine Datei gewählt +file.upload.error.toManyFiles=Es wurden zu viele Dateien für das gleichzeitige Hochladen ausgewählt. +filing=Ablageort +filter=Filter +filter.all=alle +filter.extendedSearch=Erweiterte Suche +filter.faulty=fehlerhaft +filter.newest=die neuesten finance.accountingRecord.dc=SH finance.accountingRecord.dc.credit=Haben finance.accountingRecord.dc.debit=Soll @@ -1387,6 +1267,8 @@ finance.datev.import.error.titleRowMissed=Titelzeile fehlt. ### not translated: finance.datev.upload.hint=*.xls; max. {0} finance.datev.uploadAccountingRecords=Buchungsdaten hochladen finance.datev.uploadAccountList=Konten hochladen +financeAdministration=Finanzbuchhaltung +firstName=Vorname form.ajaxEditableLabel.tooltip=Zum Editieren bitte anklicken. gantt.access.all=Alle gantt.access.owner=Eigner:in @@ -1455,6 +1337,14 @@ gantt.x.unit.day=Tag gantt.x.unit.month=Monat gantt.x.unit.quarter=Quartal gantt.x.unit.week=Woche +gender=Geschlecht +gender.diverse=divers +gender.female=weiblich +gender.male=männlich +gender.notApplicable=nicht zutreffend +gender.notKnown=unbekannt +gender.unknown=unbekannt +group=Gruppe group.assignedUsers=Assoziierte Benutzer:in group.error.groupnameAlreadyExists=Gruppenname ist bereits vergeben. group.groups=Gruppen @@ -1481,6 +1371,8 @@ history.newValue=Neuer Wert history.oldValue=Alter Wert history.propertyName=Feld history.was=war +holidays=Feiertage +hours=Stunden hr.planning.description=Tätigkeit hr.planning.entry.copyFromPredecessor=Vorgänger kopieren hr.planning.entry.error.entryDoesAlreadyExistForUserAndWeekOfYear=Dieser Eintrag kollidiert mit einem bereits vorhandenem für den gleichen Benutzer für die gleiche Kalenderwoche. @@ -1516,6 +1408,8 @@ hr.planning.unassignedHours=ohne Tagangabe hr.planning.view.title=Planungsübersicht hr.planning.weekend=Wochenende hr.planning.workdays=Arbeitstage +id=Id +imageFile=Bild import.confirmMessage=Sollen nun alle ausgewählten Einträge importiert werden? Diese Aktion kann nicht rückgängig gemacht werden. import.display.options=Anzeigeoptionen import.entry.error=Fehler @@ -1550,6 +1444,7 @@ import.title=Import-Tool index.development=Webseite für Entwicklung index.website=Webseite index.welcome=Willkommen bei ProjectForge. +inherit=vererbt jira.chooseProject=--- JIRA-Projekt wählen --- jobs.error.refusedByAnotherRunningJob=Job konnte nicht gestartet werden, weil bereits ein anderer Job für die gleiche Queue läuft. jobs.error.waitingTimeExceeded=Job fehlgeschlagen (Zeitüberschreitung). Der Job konnte nicht gestartet werden, weil er von anderen Job(s) zu lange blockiert wurde. @@ -1568,6 +1463,7 @@ jobs.job.terminatedAt=Beendet jobs.job.title=Jobtitel jobs.monitor.noJobsAvailable=Keine aktuellen Jobs jobs.monitor.title=Jobmonitor +key=Schlüssel label.data=Daten label.exportFormat=Exportformat label.filterSettings=Filtereinstellungen @@ -1585,6 +1481,8 @@ label.script=Script label.script.result=Script-Ergebnis label.sendEMailNotification=E-Mail-Benachrichtigung versenden? label.sendShortMessage=SMS an Bearbeiter versenden? +language=Sprache +lastUpdate=letzte Änderung ldap=LDAP ldap.gidNumber=GID number ldap.gidNumber.alreadyInUse=Die GID-Nummer ist bereits an eine andere Gruppe vergeben. Die nächste freie GID-Nummer lautet: {0}. @@ -1635,7 +1533,11 @@ legalAffaires.contract.type=Typ legalAffaires.contract.validity=Laufzeit legalAffaires.contract.validity.from=Laufzeit von legalAffaires.contract.validity.until=Laufzeit bis +legend=Legende license.upload.title=Lizenzdatei +list=Liste +listView=Listenansicht +loading=Laden... locale.de=Deutsch locale.en=Englisch locale.zh=Chinesisch @@ -1644,6 +1546,7 @@ locale.zh=Chinesisch ### not translated: log.level.info=Info ### not translated: log.level.trace=Trace ### not translated: log.level.warn=Warn +loggedInUserInfo=Angemeldet als: login.adminLoginRequired=Wartungsmodus: Bitte als Administrator:in anmelden! login.error.loginExpired=Die Anmeldung war nicht erfolgreich. Bitte Administrator:in kontaktieren, da der Zugang abgelaufen ist. login.error.loginFailed=Die Anmeldung war nicht erfolgreich. Bitte Eingabe nochmals prüfen (Groß-/Kleinschreibung bitte beachten). @@ -1660,6 +1563,7 @@ login.successful=Anmeldung erfolgreich. Viel Spaß mit ProjectForge! login.timeOffset=Der Zugang ist noch für {0} Sekunden gesperrt auf Grund von {1} Fehlanmeldungen. Bitte später nochmals versuchen. login.title=Anmeldung logout.successful=Erfolgreich abgemeldet. +longFormat=Langes Format mail.error.exception=Beim E-Mailversand trat ein Fehler auf, der protokolliert wurde. Bitte eine:n Systemadministrator:in kontaktieren. mail.error.missingToAddress=Der E-Mailversand wurde abgebrochen, da keine Empfänger:inadresse angegeben ist. mail.template.closing=Viel Spaß mit ProjectForge\! @@ -1798,11 +1702,35 @@ message.successfullChanged=Änderung erfolgreich durchgeführt. message.successfullCompleted=Aktion erfolgreich durchgeführt\: {0} message.title=Meldung message.wicket.pageExpired=Die aufgerufene Seite ist nicht mehr verfügbar. +misc=Verschiedenes +modifications=Änderungen +modificationTime=Änderungszeitraum +modified=geändert +modifiedBy=geändert durch +modifiedHistoryValue=Wert in der Änderungshistorie +moreEntriesAvailable=Mehr Einträge vorhanden. multiselection.aggrid.selection.info.message=\ * Gemeinsam mit der Hochstelltaste bzw. Steuerungstaste können mehrere Zeilen per Mausklick gleichzeitig ausgewählt werden.\n\ * Mit den Pfeiltasten kann in den Zeilen navigiert werden und über die Leertaste einzelne Zeilen selektiert und deselektiert werden. multiselection.aggrid.selection.info.title=Mehrfachauswahl multiselection.button=Mehrfachauswahl +name=Name +new=Neu +nickname=Rufname +no=Nein +notEnded=nicht beendet +nothingFound=Nichts gefunden. +notLoggedIn=Nicht angemeldet +notVisible=nicht sichtbar +oclock=Uhr +onlyDeleted=nur gelöschte +onlyDeleted.tooltip=Es werden nur gelöschte Datensätze angezeigt (i. d. R. unabhängig von anderen Filterangaben). +operation.deleted=gelöscht +operation.inserted=angelegt +operation.markAsDeleted=als gelöscht markiert +operation.undefined=undefiniert +operation.undeleted=wiederhergestellt +operation.updated=geändert orga.post.inhalt=Inhalt orga.post.type=Art orga.post.type.brief=Brief @@ -1845,10 +1773,12 @@ orga.visitorbook.visitortype.family=Familie orga.visitorbook.visitortype.normal=Normal orga.visitorbook.visitortype.supplier=Lieferant:in orga.visitorbook.visitortype.workman=Handwerker:in +organization=Firma pacman.title=Pacman panel.error.customernameNotFound=Kundenname nicht existent. panel.error.groupNotFound=Gruppe nicht existent. panel.error.projectNotFound=Projekt nicht existent. +password=Passwort password.forgotten.link=Zugangsdaten vergessen? password.forgotten.mail.subject=Passwortreset ProjectForge® password.forgotten.mailSentTo=E-Mail mit dem Passwortrücksetzen-Link wurde gesendet an ''{0}''. Bitte im Spam-Ordner schauen bzw. überprüfen, ob der Benutzer:innenname bzw. E-Mail korrekt ist. @@ -1860,10 +1790,13 @@ password.reset.mail.message.1=Bitte beachten: Es wird für das Zurücksetzen aus password.reset.mail.message.2=Wenn noch kein zweiter, persönlicher Faktor konfiguriert wurde (Authenticator-App oder Mobilfunknummer), muss ein ProjectForge-Administrator kontaktiert werden. password.reset.title=Passwort zurücksetzen password.reset.username_email=Benutzer:inname/E-Mail +passwordRepeat=Passwort wiederholen +percent=Prozent personal.statistics.timesheetDisciplineChart.title=Zeitnahes Erfassen von Zeitberichten ist ein wichtiger Grundpfeiler erfolgreichen Projektmanagements! personal.statistics.timesheetDisciplineChart1.legend=Insgesamt sind {1} Arbeitszeitstunden in den letzten {0} Tagen angefallen. Bisher wurden für diesen Zeitraum {2} Stunden erfasst. personal.statistics.timesheetDisciplineChart2.legend=In den letzten {0} Tagen wurden Zeitberichte nach durchschnittlich {2} Tagen erfasst. Die Zielgröße ist maximal {1} Tage. personal.statistics.title=Meine Statistiken +pleaseChoose=Bitte wählen plugins.teamcal.access=Zugriffsrecht plugins.teamcal.access.groups=Gruppen plugins.teamcal.access.title=Zugriffsoptionen @@ -2056,78 +1989,161 @@ plugins.teamcal.title.add=Kalender hinzufügen plugins.teamcal.title.edit=Team-Kalender bearbeiten plugins.teamcal.title.heading=Kalender plugins.teamcal.title.list=Kalenderliste +printView=Druckansicht +priority=Priorität +priority.high=hoch +priority.highest=sehr hoch +priority.least=gering +priority.low=niedrig +priority.middle=mittel +priority.without=ohne +quickselect=Schnellauswahl +recursive=rekursiv +rest=Rest +resubmissionOnDate=Wiedervorlage +resumption=Wiedervorlage +searchFilter=Suchfilter +searchString=Suchtext +securityAdvice=Sicherheitshinweis +settings=Einstellungen +shortDescription=Kurzbeschreibung +sql=SQL +statistics=Statistik +status=Status +sum=Summe +task=Strukturelement +templates=Vorlagen +templates.new=Neue Vorlage +text=Text +timeago.afewseconds=vor einigen Sekunden +timeago.days=vor {0} Tagen +timeago.days.one=gestern +timeago.hours=vor {0} Stunden +timeago.hours.one=vor einer Stunde +timeago.minutes=vor {0} Minuten +timeago.minutes.one=vor einer Minute +timeago.months=vor {0} Monaten +timeago.months.one=vor einem Monat +timeago.negative=in der Zukunft! +timeago.seconds=vor {0} Sekunden +timeago.weeks=vor {0} Wochen +timeago.weeks.one=vor einer Woche +timeago.years=vor {0} Jahren +timeago.years.one=vor einem Jahr +timeleft.afewseconds=in einigen Sekunden +timeleft.days=in {0} Tagen +timeleft.days.one=in einem Tag +timeleft.hours=in {0} Stunden +timeleft.hours.one=in einer Stunde +timeleft.minutes=in {0} Minuten +timeleft.minutes.one=in einer Minute +timeleft.months=in {0} Monaten +timeleft.months.one=in einem Monat +timeleft.negative=in der Vergangenheit! +timeleft.seconds=in {0} Sekunden +timeleft.weeks=in {0} Wochen +timeleft.weeks.one=in einer Woche +timeleft.years=in {0} Jahren +timeleft.years.one=in einem Jahr +timeNotation=Uhrzeitformat +timeNotation.12=12 Stunden +timeNotation.24=24 Stunden +timeOfCreation=Anlagezeitpunkt +timeOfLastUpdate=Zeitpunkt der letzten Änderung +timePeriod=Zeitraum +timestamp=Zeitstempel +timezone=Zeitzone +tip=Tipp +title=Titel +totalSum=Gesamtsumme +undefined=undefiniert +until=bis +untitled=unbenannt +user=Benutzer:in +username=Benutzer:innenname +value=Wert +values=Werte +weekOfYear=Kalenderwoche +yes=Ja # poll plugin poll=Umfrage -poll.title=Titel -poll.title.list=Umfragen -poll.title.add=Neue Umfrage erstellen -poll.title.edit=Umfrage bearbeiten -poll.owner=Eigentümer:in -poll.description=Beschreibung -poll.attendees=Teilnehmer:innen -poll.groupAttendees=Teilnehmergruppen -poll.fullAccessUsers=Benutzer:innen mit Vollzugriff -poll.fullAccessGroups=Gruppen mit Vollzugriff -poll.date=Datum -poll.deadline=Antwortfrist -poll.location=Ort -poll.state=Status -poll.assignment=Zuordnung poll.access=Zugriff -poll.attendee=Teilnehmer:in -poll.other=Andere -poll.running=Aktiv -poll.finished=Beendet -poll.infopage=Infoseite -poll.guide=Anleitung -poll.question=Frage -poll.question.textQuestion=Textfrage -poll.questionType=Fragen Typ poll.answer=Antwort -poll.yourAnswers=Deine Antworten -poll.delegationAnswers=Antworten von -poll.button.addQuestion=Eigene Frage hinzufügen -poll.button.micromataTemplate=Micromata Template verwenden +poll.assignment=Zuordnung +poll.attendee=Teilnehmer:in +poll.attendees=Teilnehmer:innen +poll.button.addQuestion=Eigene Frage hinzuf�gen poll.button.finish=Umfrage beenden +poll.button.micromataTemplate=Micromata Template verwenden +poll.confirmation.creation=M�chtest du die Umfrage wirklich erstellen? Du kannst die Fragen danach nicht mehr bearbeiten.\ +Stelle ebenfalls sicher, dass du Teilnehmer f�r deine Umfrage hinzugef�gt hast. +poll.confirmation.deleteAnswer=M�chtest du diese Antwort wirklich l�schen? +poll.confirmation.deleteQuestion=M�chtest du diese Frage wirklich l�schen? poll.confirmation.finish=Willst du die Umfrage wirklich beenden? -poll.confirmation.creation=Möchtest du die Umfrage wirklich erstellen? Du kannst die Fragen danach nicht mehr bearbeiten.\ -Stelle ebenfalls sicher, dass du Teilnehmer für deine Umfrage hinzugefügt hast. -poll.confirmation.deleteQuestion=Möchtest du diese Frage wirklich löschen? -poll.confirmation.deleteAnswer=Möchtest du diese Antwort wirklich löschen? -poll.error.closed=Umfrage wurde bereits beendet +poll.date=Datum +poll.deadline=Antwortfrist +poll.delegationAnswers=Antworten von +poll.description=Beschreibung poll.error.oneQuestionRequired=Mindestens eine Frage ist erforderlich. -poll.export.response.poll=Ergebnisse exportieren -poll.popup.closed=Umfrage wurde bereits beendet -poll.respond=Antworten abschicken -poll.response.page=Seite zur Umfrageantwort -poll.response.title=Seite zur Umfrageantwort -poll.userDelegation=Für andere Nutzer abstimmen -poll.selectUser=Nutzer auswählen poll.exception.noAttendee=Dieser Nutzer ist nicht Teil der Umfrage. -poll.mail.endingSoon.subject=Umfrage endet in {0} Tagen -poll.mail.endingSoon.content=

Liebe Teilnehmerinnen und Teilnehmer,

\ -

wir möchten Sie daran erinnern, dass die Umfrage "{0}", erstellt von {1}, bald endet, nämlich am {2}. Bitte achten Sie darauf, Ihre Antworten vor dem Ablaufdatum einzureichen.

\ -

Falls Sie noch nicht die Gelegenheit hatten, an der Umfrage teilzunehmen, nehmen Sie sich bitte einen Moment Zeit, um dies zu tun, bevor die Umfrage geschlossen wird. Ihre Meinung ist wichtig und wertvoll.

\ -

{3}

\ -

Vielen Dank für Ihre Aufmerksamkeit und einen schönen Tag!

\ +poll.export.response.poll=Ergebnisse exportieren +poll.finished=Beendet +poll.fullAccessGroups=Gruppen mit Vollzugriff +poll.fullAccessUsers=Benutzer:innen mit Vollzugriff +poll.groupAttendees=Teilnehmergruppen +poll.guide=Anleitung +poll.infopage=Infoseite +poll.location=Ort +poll.mail.ended.content=

Liebe Teilnehmerinnen und Teilnehmer,

\ +

wir m�chten Sie dar�ber informieren, dass die Umfrage "{0}", erstellt von {1}, nun abgeschlossen ist. Vielen Dank an alle, die teilgenommen und wertvolles Feedback gegeben haben.

\ +

Falls Sie den Einsendeschluss verpasst haben, m�chten wir Sie dennoch ermutigen, uns Ihre Gedanken mitzuteilen. Auch wenn wir Ihre Antworten m�glicherweise nicht in den offiziellen Ergebnissen ber�cksichtigen k�nnen, ist Ihr Feedback dennoch wertvoll f�r zuk�nftige Umfragen und Initiativen.

\ +

Nochmals vielen Dank f�r Ihre Teilnahme.

\
\ -

Freundliche Grüße,

\ +

Freundliche Gr��e,

\

{1}

poll.mail.ended.subject=Umfrage beendet -poll.mail.ended.content=

Liebe Teilnehmerinnen und Teilnehmer,

\ -

wir möchten Sie darüber informieren, dass die Umfrage "{0}", erstellt von {1}, nun abgeschlossen ist. Vielen Dank an alle, die teilgenommen und wertvolles Feedback gegeben haben.

\ -

Falls Sie den Einsendeschluss verpasst haben, möchten wir Sie dennoch ermutigen, uns Ihre Gedanken mitzuteilen. Auch wenn wir Ihre Antworten möglicherweise nicht in den offiziellen Ergebnissen berücksichtigen können, ist Ihr Feedback dennoch wertvoll für zukünftige Umfragen und Initiativen.

\ -

Nochmals vielen Dank für Ihre Teilnahme.

\ +poll.mail.endingSoon.content=

Liebe Teilnehmerinnen und Teilnehmer,

\ +

wir m�chten Sie daran erinnern, dass die Umfrage "{0}", erstellt von {1}, bald endet, n�mlich am {2}. Bitte achten Sie darauf, Ihre Antworten vor dem Ablaufdatum einzureichen.

\ +

Falls Sie noch nicht die Gelegenheit hatten, an der Umfrage teilzunehmen, nehmen Sie sich bitte einen Moment Zeit, um dies zu tun, bevor die Umfrage geschlossen wird. Ihre Meinung ist wichtig und wertvoll.

\ +

{3}

\ +

Vielen Dank f�r Ihre Aufmerksamkeit und einen sch�nen Tag!

\
\ -

Freundliche Grüße,

\ +

Freundliche Gr��e,

\

{1}

-poll.mail.update.subject=Umfrage wurde bearbeitet +poll.mail.endingSoon.subject=Umfrage endet in {0} Tagen poll.mail.update.content=

Liebe Teilnehmerinnen und Teilnehmer,

\ -

Wir möchten Ihnen mitteilen, dass die Umfrage "{0}" kürzlich bearbeitet wurde.

\ -

Falls Sie bereits Ihre Antworten eingereicht haben, sollten Sie überprüfen, ob wesentliche Änderungen vorgenommen wurden.

\ -

Nochmals vielen Dank für Ihre Teilnahme.

\ -

Freundliche Grüße,

\ +

Wir m�chten Ihnen mitteilen, dass die Umfrage "{0}" k�rzlich bearbeitet wurde.

\ +

Falls Sie bereits Ihre Antworten eingereicht haben, sollten Sie �berpr�fen, ob wesentliche �nderungen vorgenommen wurden.

\ +

Nochmals vielen Dank f�r Ihre Teilnahme.

\ +

Freundliche Gr��e,

\

{1}

+poll.mail.update.subject=Umfrage wurde bearbeitet +poll.other=Andere +poll.owner=Eigent�mer:in +poll.popup.closed=Umfrage wurde bereits beendet +poll.question=Frage +poll.question.textQuestion=Textfrage +poll.questionType=Fragen Typ +poll.respond=Antworten abschicken +### not translated: poll.response.mail.update.content=

Dear {0},

\ +#

I wanted to inform you that Person {2} has updated their answer to Poll {1}.

\ +#

If you were not notified about this,

\ +#

I recommend reaching out to the person directly or adjusting your results accordingly.

\ +#

You can modify your response until "{3}".

\ +#

Best regards,

\ +#

{2}

+### not translated: poll.response.mail.update.subject=Response was edited by {0} +poll.response.page=Seite zur Umfrageantwort +poll.response.title=Seite zur Umfrageantwort +poll.running=Aktiv +poll.selectUser=Nutzer ausw�hlen +poll.state=Status +poll.title=Titel +poll.title.add=Neue Umfrage erstellen +poll.title.edit=Umfrage bearbeiten +poll.title.list=Umfragen +poll.userDelegation=F�r andere Nutzer abstimmen +poll.yourAnswers=Deine Antworten projectmanagement.personDays=Personentage projectmanagement.personDays.short=PT question.deleteQuestion=Soll das Objekt wirklich unwiderruflich gelöscht werden? diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index 8a136996a7..8eeb5f0d15 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -1,3 +1,26 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.rest.poll import com.fasterxml.jackson.databind.ObjectMapper @@ -55,4 +78,4 @@ class Poll( fun isFinished(): Boolean { return state == PollDO.State.FINISHED } -} \ No newline at end of file +} diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt index 51efbbde3f..f91b306f58 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt @@ -1,3 +1,26 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.rest.poll import org.projectforge.business.poll.PollDO @@ -6,6 +29,8 @@ import org.projectforge.business.poll.PollResponseDao import org.projectforge.framework.i18n.translateMsg import org.projectforge.mail.MailAttachment import org.projectforge.rest.poll.excel.ExcelExport +import org.slf4j.Logger +import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.scheduling.annotation.Scheduled import org.springframework.web.bind.annotation.RestController @@ -30,11 +55,16 @@ class PollCronJobs { @Autowired private lateinit var exporter: ExcelExport + private val log: Logger = LoggerFactory.getLogger(PollCronJobs::class.java) + /** * Cron job for daily stuff */ - @Scheduled(cron = "0 0 1 * * *") // 1am everyday + + /*@Scheduled(cron = "0 0 1 * * *") // 1am everyday*/ + @Scheduled(cron = "0 * * * * *") // every minute for testing fun dailyCronJobs() { + log.info("Start daily cron jobs") cronDeletePolls() cronEndPolls() } @@ -46,7 +76,7 @@ class PollCronJobs { val pollDOs = pollDao.internalLoadAll() // set State.FINISHED for all old polls and export excel pollDOs.forEach { pollDO -> - if (pollDO.deadline?.isBefore(LocalDate.now()) == true) { + if (pollDO.deadline?.isBefore(LocalDate.now()) == true && pollDO.state != PollDO.State.FINISHED) { pollDO.state = PollDO.State.FINISHED val poll = Poll() @@ -64,12 +94,14 @@ class PollCronJobs { } } // add all attendees mails - val mailTo: ArrayList = ArrayList(poll.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) + val mailTo: ArrayList = + ArrayList(poll.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) val mailFrom = pollDO.owner?.email.toString() val mailSubject = translateMsg("poll.mail.ended.subject") val mailContent = translateMsg("poll.mail.ended.content", pollDO.title, pollDO.owner?.displayName) pollDao.internalSaveOrUpdate(pollDO) + log.info("Set state of poll (${pollDO.id}) ${pollDO.title} to FINISHED") pollMailService.sendMail(mailFrom, mailTo, mailContent, mailSubject, listOf(mailAttachment)) } } @@ -81,7 +113,8 @@ class PollCronJobs { val daysDifference = ChronoUnit.DAYS.between(LocalDate.now(), pollDO.deadline) if (daysDifference == 1L || daysDifference == 7L) { // add all attendees mails - val mailTo: ArrayList = ArrayList(poll.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) + val mailTo: ArrayList = + ArrayList(poll.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) val mailFrom = pollDO.owner?.email.toString() val mailSubject = translateMsg("poll.mail.endingSoon.subject", daysDifference) val mailContent = translateMsg( @@ -114,4 +147,4 @@ class PollCronJobs { pollDao.internalMarkAsDeleted(poll) } } -} \ No newline at end of file +} diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt index 537967acb0..9763846438 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt @@ -1,3 +1,26 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.rest.poll import org.projectforge.rest.config.Rest @@ -64,13 +87,25 @@ class PollInfoPageRest : AbstractDynamicPageRest() { layout.add( UIFieldset().add(UILabel("YesNoQuestion")).add( UICol() - .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage, die mit Ja oder Nein beantwortet werden kann.")) + .add( + UIReadOnlyField( + "question", + label = "Question", + value = "Eine Frage, die mit Ja oder Nein beantwortet werden kann." + ) + ) ) ) layout.add( UIFieldset().add(UILabel("MultipleChoiceQuestion")).add( UICol() - .add(UIReadOnlyField("question", label = "Question", value = "Eine Frage, die mit mehreren Antworten beantwortet werden kann.")) + .add( + UIReadOnlyField( + "question", + label = "Question", + value = "Eine Frage, die mit mehreren Antworten beantwortet werden kann." + ) + ) ) ) layout.add( @@ -103,4 +138,4 @@ class PollInfoPageRest : AbstractDynamicPageRest() { return FormLayoutData(null, layout, createServerData(request)) } -} \ No newline at end of file +} diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt index 38bc3d6b17..94bf043426 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt @@ -1,3 +1,26 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.rest.poll import org.projectforge.mail.Mail @@ -16,7 +39,13 @@ class PollMailService { private val log: Logger = LoggerFactory.getLogger(PollMailService::class.java) - fun sendMail(from: String, to: List, subject: String, content: String, mailAttachments: List? = null) { + fun sendMail( + from: String, + to: List, + subject: String, + content: String, + mailAttachments: List? = null + ) { try { if (content.isNotEmpty() && to.isNotEmpty()) { val mail = Mail() @@ -26,6 +55,9 @@ class PollMailService { mail.from = from to.forEach { mail.addTo(it) } sendMail.send(mail, attachments = mailAttachments) + log.info("Mail with subject $subject sent to $to") + } else { + log.error("There are missing parameters for sending mail: from: $from, to: $to, subject: $subject, content: $content") } } catch (e: Exception) { log.error(e.toString()) @@ -33,4 +65,4 @@ class PollMailService { } -} \ No newline at end of file +} diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index b6fa8cf234..0d5c6755d8 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -1,3 +1,26 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.rest.poll import com.fasterxml.jackson.databind.ObjectMapper @@ -72,7 +95,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun onBeforeMarkAsDeleted(request: HttpServletRequest, obj: PollDO, postData: PostData) { val responsesToDelete = pollResponseDao.internalLoadAll().filter { - it.poll!!.id == obj.id + it.poll?.id == obj.id } responsesToDelete.forEach { pollResponseDao.markAsDeleted(it) @@ -280,7 +303,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ): ResponseEntity { postData.data.state = PollDO.State.FINISHED postData.data.deadline = LocalDate.now() - return super.saveOrUpdate(request, postData) } @@ -384,7 +406,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } dto.owner = userService.getUser(dto.owner?.id) - // I dont know why this is necessary + // I don't know why this is necessary if (watchFieldsTriggered?.get(0) == "delegationUser") { dto.delegationUser = dto.delegationUser } @@ -568,9 +590,10 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val filename = ("${poll.title}_${LocalDateTime.now().year}_Result.xlsx") if (bytes == null || bytes.isEmpty()) { - log.error("Oops, xlsx has zero size. Filename: $filename") + log.error("Oops, xlsx is empty. Filename: $filename") return null } + log.info("Exporting $filename") return RestUtils.downloadFile(filename, bytes) } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 3da940693d..1eefa42016 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -1,3 +1,26 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.rest.poll import com.fasterxml.jackson.databind.ObjectMapper @@ -58,13 +81,13 @@ class ResponsePageRest : AbstractDynamicPageRest() { @RequestParam("questionOwner") delUser: String?, @RequestParam("returnToCaller") returnToCaller: String?, ): FormLayoutData { - if (pollId === null || (pollStringId != null && pollId != null)) { + if (pollId === null || pollStringId != null) { pollId = NumberHelper.parseInteger(pollStringId) ?: throw IllegalArgumentException("id not given.") } // used to load answers, is an attendee chosen by a fullAccessUser in order to answer for them or the ThreadLocal User val pollData = pollDao.internalGetById(pollId) ?: PollDO() - var answerTitle = "" + val answerTitle: String if (delUser != null && pollDao.hasFullAccess(pollData) && pollDao.isAttendee(pollData, delUser.toInt())) { questionOwnerId = delUser.toInt() answerTitle = translateMsg("poll.delegationAnswers") + userService.getUser(questionOwnerId).displayName @@ -229,7 +252,6 @@ class ResponsePageRest : AbstractDynamicPageRest() { return FormLayoutData(pollResponse, layout, createServerData(request)) } - @PostMapping("addResponse") fun addResponse( request: HttpServletRequest, @@ -267,12 +289,11 @@ class ResponsePageRest : AbstractDynamicPageRest() { ) } - private fun sendMailResponseToOwner(pollResponseDO: PollResponseDO, canedUser: PFUserDO) { - var emailList = ArrayList() + val emailList = ArrayList() pollResponseDO.owner?.email?.let { emailList.add(it) } pollMailService.sendMail( - canedUser?.email.toString(), emailList, + canedUser.email.toString(), emailList, translateMsg("poll.response.mail.update.subject", canedUser.displayName), translateMsg( "poll.response.mail.update.content", @@ -284,18 +305,17 @@ class ResponsePageRest : AbstractDynamicPageRest() { ) } - @GetMapping("showDelegatedUser") fun showDelegatedUser( request: HttpServletRequest ): ResponseEntity? { - var attendees = listOf( + val attendees = listOf( pollDao.internalGetById(pollId).attendeeIds, pollDao.internalGetById(pollId).fullAccessUserIds, pollDao.internalGetById(pollId).owner?.id ) - var joinedAttendeeIds = attendees.joinToString(", ") - if (attendees != null && joinedAttendeeIds.split(", ").any { it.toIntOrNull() == questionOwnerId }) { + val joinedAttendeeIds = attendees.joinToString(", ") + if (joinedAttendeeIds.split(", ").any { it.toIntOrNull() == questionOwnerId }) { return ResponseEntity.ok( ResponseAction( url = "/react/response/dynamic?pollId=${pollId}&questionOwner=${questionOwnerId}", @@ -312,7 +332,6 @@ class ResponsePageRest : AbstractDynamicPageRest() { return ResponseEntity.ok(ResponseAction(targetType = TargetType.UPDATE)) } - private fun transformPollFromDB(obj: PollDO): Poll { val poll = Poll() poll.copyFrom(obj) @@ -323,4 +342,4 @@ class ResponsePageRest : AbstractDynamicPageRest() { return poll } -} \ No newline at end of file +} diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt index 036648f6ab..c81ccea332 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt @@ -1,3 +1,26 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.rest.poll.excel import de.micromata.merlin.excel.ExcelRow @@ -29,7 +52,6 @@ class ExcelExport { @Autowired private lateinit var pollResponseDao: PollResponseDao - fun getExcel(poll: Poll): ByteArray? { val responses = pollResponseDao.internalLoadAll().filter { it.poll?.id == poll.id } val classPathResource = ClassPathResource("officeTemplates/PollResultTemplate" + ".xlsx") @@ -129,7 +151,8 @@ class ExcelExport { var counterOfBreaking = 0 var counterOfOverlength = 0 - val pufferSplit: Array = puffer.split("\r\n|\r|\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val pufferSplit: Array = + puffer.split("\r\n|\r|\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() // check for line-breaks for (i in pufferSplit.indices) { counterOfBreaking++ @@ -164,4 +187,4 @@ class ExcelExport { return byteArrayOutputStream.toByteArray() } } -} \ No newline at end of file +} diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt index f03406cb66..39cd28a03c 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt @@ -1,3 +1,26 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.rest.poll.types import com.fasterxml.jackson.databind.ObjectMapper @@ -35,4 +58,4 @@ class QuestionAnswer { fun toObject(string: String): QuestionAnswer { return ObjectMapper().readValue(string, QuestionAnswer::class.java) } -} \ No newline at end of file +} diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt index d1e47339cc..9caac1bc82 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PremadeQuestions.kt @@ -1,3 +1,26 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.rest.poll.types import java.util.UUID diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt index 019c76accb..fcdc45c6a1 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/Question.kt @@ -1,3 +1,26 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2023 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + package org.projectforge.rest.poll.types import com.fasterxml.jackson.databind.ObjectMapper @@ -23,4 +46,4 @@ class Question( enum class BaseType { TextQuestion, SingleResponseQuestion, MultiResponseQuestion, -} \ No newline at end of file +} From 4dce895275a453d05dc42eac410a6f0c0f963a27 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 21 Jul 2023 10:27:28 +0200 Subject: [PATCH 132/160] fix excel export --- .../kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt index 5cc6c49d31..f915294076 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt @@ -13,7 +13,6 @@ import org.projectforge.rest.dto.User import org.projectforge.rest.poll.Poll import org.projectforge.rest.poll.types.BaseType import org.projectforge.rest.poll.types.PollResponse -import org.projectforge.web.rest.converter.PFUserDOConverter import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired @@ -53,7 +52,6 @@ class ExcelExport { anzNewRows += (poll.attendees?.size ?: 0) - createNewRow(excelSheet, emptyRow, anzNewRows) setFirstRow(excelSheet, style, poll) @@ -83,9 +81,10 @@ class ExcelExport { if (owner != null) { fullAccessUser.add(owner) } + User.restoreDisplayNames(fullAccessUser, userService) fullAccessUser.forEachIndexed { index, user -> - var number = (index) + (anzNewRows) + var number = (anzNewRows) if (poll.attendees?.map { it.id }?.contains(user.id) == false) { val res = PollResponse() responses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } @@ -93,6 +92,7 @@ class ExcelExport { if (res.id != null) { // Put Data's in the Row setNewRows(excelSheet, poll, user, res, number) + number++ } } } @@ -107,6 +107,7 @@ class ExcelExport { return null } + private fun setFirstRow(excelSheet: ExcelSheet, style: CellStyle, poll: Poll) { val excelRow = excelSheet.getRow(0) val excelRow1 = excelSheet.getRow(1) From 136b268a8a94379d190d37e488b7b1c8297c6e53 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Fri, 21 Jul 2023 10:29:35 +0200 Subject: [PATCH 133/160] get allMails in Mail service --- .../projectforge/rest/poll/PollCronJobs.kt | 6 ++- .../projectforge/rest/poll/PollMailService.kt | 44 ++++++++++++++++++- .../projectforge/rest/poll/PollPageRest.kt | 7 ++- 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt index 51efbbde3f..669253d632 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt @@ -64,7 +64,8 @@ class PollCronJobs { } } // add all attendees mails - val mailTo: ArrayList = ArrayList(poll.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) + val mailTo: ArrayList = + ArrayList(poll.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) val mailFrom = pollDO.owner?.email.toString() val mailSubject = translateMsg("poll.mail.ended.subject") val mailContent = translateMsg("poll.mail.ended.content", pollDO.title, pollDO.owner?.displayName) @@ -81,7 +82,8 @@ class PollCronJobs { val daysDifference = ChronoUnit.DAYS.between(LocalDate.now(), pollDO.deadline) if (daysDifference == 1L || daysDifference == 7L) { // add all attendees mails - val mailTo: ArrayList = ArrayList(poll.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) + val mailTo: ArrayList = + ArrayList(poll.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) val mailFrom = pollDO.owner?.email.toString() val mailSubject = translateMsg("poll.mail.endingSoon.subject", daysDifference) val mailContent = translateMsg( diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt index 38bc3d6b17..ff3f8c6651 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt @@ -1,8 +1,11 @@ package org.projectforge.rest.poll +import org.projectforge.business.group.service.GroupService +import org.projectforge.business.user.service.UserService import org.projectforge.mail.Mail import org.projectforge.mail.MailAttachment import org.projectforge.mail.SendMail +import org.projectforge.rest.dto.User import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired @@ -14,9 +17,21 @@ class PollMailService { @Autowired private lateinit var sendMail: SendMail + @Autowired + private lateinit var groupService: GroupService + + @Autowired + private lateinit var userService: UserService + private val log: Logger = LoggerFactory.getLogger(PollMailService::class.java) - fun sendMail(from: String, to: List, subject: String, content: String, mailAttachments: List? = null) { + fun sendMail( + from: String, + to: List, + subject: String, + content: String, + mailAttachments: List? = null + ) { try { if (content.isNotEmpty() && to.isNotEmpty()) { val mail = Mail() @@ -33,4 +48,31 @@ class PollMailService { } + fun getAllMails(poll: Poll): List { + val attendees = poll.attendees + var fullAccessUser = poll.fullAccessUsers?.toMutableList() ?: mutableListOf() + val accessGroupIds = poll.fullAccessGroups?.filter { it.id != null }?.map { it.id!! }?.toIntArray() + val accessUserIds = UserService().getUserIds(groupService.getGroupUsers(accessGroupIds)) + val accessUsers = User.toUserList(accessUserIds) + + accessUsers?.forEach { user -> + if (fullAccessUser.none { it.id == user.id }) { + fullAccessUser.add(user) + } + } + + var owner = User.getUser(poll.owner?.id, false) + if (owner != null) { + fullAccessUser.add(owner) + } + attendees?.forEach { + if (!fullAccessUser.contains(it)) { + fullAccessUser.add(it) + } + } + + User.restoreDisplayNames(fullAccessUser, userService) + return fullAccessUser.mapNotNull { it.email } + } + } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 43a92c3902..fa98f5df88 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -296,8 +296,10 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. override fun onAfterSaveOrUpdate(request: HttpServletRequest, obj: PollDO, postData: PostData) { // add all attendees mails - val mailTo: ArrayList = - ArrayList(postData.data.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) + + + var mailTo = pollMailService.getAllMails(postData.data) + val owner = userService.getUser(obj.owner?.id) val mailFrom = owner?.email.toString() val mailSubject: String @@ -647,4 +649,5 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. } } + } From cdb862e099c96dffe1b80e98a4feb5c56546980f Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Fri, 21 Jul 2023 13:22:51 +0200 Subject: [PATCH 134/160] fixed delegation bugs and contains() mistake on string --- .../org/projectforge/business/poll/PollDao.kt | 18 ++++++++++++------ .../projectforge/rest/poll/ResponsePageRest.kt | 18 +++++++++++++++--- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index e7cd2cc46f..305488845d 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -40,17 +40,23 @@ open class PollDao : BaseDao(PollDO::class.java) { return false } + //returns true if current user hast full access, otherwise returns false fun hasFullAccess(obj: PollDO): Boolean { - val loggedInUser = user - if (!obj.fullAccessUserIds.isNullOrBlank() && obj.fullAccessUserIds!!.contains(loggedInUser?.id.toString())) - return true - if (obj.owner?.id == loggedInUser?.id) + val loggedInUserId = ThreadLocalUserContext.userId!! + if (!obj.fullAccessUserIds.isNullOrBlank()) { + val userIdArray = obj.fullAccessGroupIds!!.split(", ").map { it.toInt() }.toIntArray() + if (userIdArray.contains(loggedInUserId)) + return true + } + if (obj.owner?.id == loggedInUserId) return true if (!obj.fullAccessGroupIds.isNullOrBlank()) { val groupIdArray = obj.fullAccessGroupIds!!.split(", ").map { it.toInt() }.toIntArray() val groupUsers = groupService?.getGroupUsers(groupIdArray) - if (groupUsers?.contains(loggedInUser) == true) - return true + groupUsers!!.map { it.id }.forEach { + if (it == loggedInUserId) + return true + } } return false } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 3da940693d..2bfd3d484f 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -289,20 +289,32 @@ class ResponsePageRest : AbstractDynamicPageRest() { fun showDelegatedUser( request: HttpServletRequest ): ResponseEntity? { - var attendees = listOf( + var attendees = listOfNotNull( pollDao.internalGetById(pollId).attendeeIds, pollDao.internalGetById(pollId).fullAccessUserIds, pollDao.internalGetById(pollId).owner?.id ) var joinedAttendeeIds = attendees.joinToString(", ") - if (attendees != null && joinedAttendeeIds.split(", ").any { it.toIntOrNull() == questionOwnerId }) { + if (questionOwnerId == ThreadLocalUserContext.userId) { + return ResponseEntity.ok( + ResponseAction() + ) + } + if (joinedAttendeeIds.split(", ").any { it.toInt() == questionOwnerId }) { return ResponseEntity.ok( ResponseAction( url = "/react/response/dynamic?pollId=${pollId}&questionOwner=${questionOwnerId}", targetType = TargetType.REDIRECT ) ) - } else throw AccessException("poll.exception.noAttendee") + } else { + return ResponseEntity.badRequest().body( + ResponseAction( + url = "/react/poll", + targetType = TargetType.REDIRECT, + message = ResponseAction.Message("poll.exception.noAttendee") + )) + } } From 4e4d94bc4f704ab93f94cbff3c3d40976f823d5b Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Fri, 21 Jul 2023 13:43:40 +0200 Subject: [PATCH 135/160] delegated user can now be attendee OR full access user --- .../org/projectforge/business/poll/PollDao.kt | 13 ++++++------- .../org/projectforge/rest/poll/PollPageRest.kt | 2 +- .../org/projectforge/rest/poll/ResponsePageRest.kt | 5 +++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index e7cd2cc46f..a55f9c7cb5 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -31,25 +31,24 @@ open class PollDao : BaseDao(PollDO::class.java) { return true }; if (obj != null && operationType == OperationType.SELECT){ - if(hasFullAccess(obj) || isAttendee(obj, ThreadLocalUserContext.user?.id!!)) + if(hasFullAccess(obj, ThreadLocalUserContext.user!!) || isAttendee(obj, ThreadLocalUserContext.user?.id!!)) return true } if (obj != null) { - return hasFullAccess(obj) + return hasFullAccess(obj, ThreadLocalUserContext.user!!) } return false } - fun hasFullAccess(obj: PollDO): Boolean { - val loggedInUser = user - if (!obj.fullAccessUserIds.isNullOrBlank() && obj.fullAccessUserIds!!.contains(loggedInUser?.id.toString())) + fun hasFullAccess(obj: PollDO, user: PFUserDO): Boolean { + if (!obj.fullAccessUserIds.isNullOrBlank() && obj.fullAccessUserIds!!.contains(user.id.toString())) return true - if (obj.owner?.id == loggedInUser?.id) + if (obj.owner?.id == user.id) return true if (!obj.fullAccessGroupIds.isNullOrBlank()) { val groupIdArray = obj.fullAccessGroupIds!!.split(", ").map { it.toInt() }.toIntArray() val groupUsers = groupService?.getGroupUsers(groupIdArray) - if (groupUsers?.contains(loggedInUser) == true) + if (groupUsers?.contains(user) == true) return true } return false diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 3a555d3930..9812404b6f 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -636,7 +636,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val pollDO = PollDO() pollDto.copyTo(pollDO) - return if (!pollDao.hasFullAccess(pollDO)) { + return if (!pollDao.hasFullAccess(pollDO, ThreadLocalUserContext.user!!)) { // no full access user UILayout.UserAccess(insert = false, update = false, delete = false, history = false) } else { diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 3da940693d..0f9fe6ca70 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -65,7 +65,8 @@ class ResponsePageRest : AbstractDynamicPageRest() { val pollData = pollDao.internalGetById(pollId) ?: PollDO() var answerTitle = "" - if (delUser != null && pollDao.hasFullAccess(pollData) && pollDao.isAttendee(pollData, delUser.toInt())) { + //check if the delegation user is an attendee or has full access and the one delegating has also full access + if (delUser != null && pollDao.hasFullAccess(pollData, ThreadLocalUserContext.user!!) && (pollDao.isAttendee(pollData, delUser.toInt()) || pollDao.hasFullAccess(pollData, userService.getUser(questionOwnerId)))) { questionOwnerId = delUser.toInt() answerTitle = translateMsg("poll.delegationAnswers") + userService.getUser(questionOwnerId).displayName } else { @@ -86,7 +87,7 @@ class ResponsePageRest : AbstractDynamicPageRest() { } val layout = UILayout("poll.response.title") - if (pollDao.hasFullAccess(pollData)) { + if (pollDao.hasFullAccess(pollData, ThreadLocalUserContext.user!!)) { layout.add( MenuItem( "EDIT", From 6034265fcc18c510352a11c06bda03134c80c2ce Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Fri, 21 Jul 2023 13:58:22 +0200 Subject: [PATCH 136/160] prevent npe --- .../main/kotlin/org/projectforge/business/poll/PollDO.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index dbc6b03e50..1fdc256157 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -104,15 +104,16 @@ open class PollDO : DefaultBaseDO() { if (accessUserIds?.contains(currentUserId) == true) { assignmentList.add(PollAssignment.ACCESS) } + if (!this.fullAccessUserIds.isNullOrBlank()) { - val accessUserIds = this.fullAccessUserIds!!.split(", ").map { it.toInt() }.toIntArray() - if (accessUserIds.contains(currentUserId)) { + val accessUserIds = toIntArray(this.fullAccessUserIds) + if (accessUserIds?.contains(currentUserId) == true) { assignmentList.add(PollAssignment.ACCESS) } } if (!this.attendeeIds.isNullOrBlank()) { - val attendeeUserIds = this.attendeeIds!!.split(", ").map { it.toInt() }.toIntArray() - if (attendeeUserIds.contains(currentUserId)) { + val attendeeUserIds = toIntArray(this.attendeeIds) + if (attendeeUserIds?.contains(currentUserId) == true) { assignmentList.add(PollAssignment.ATTENDEE) } } From 598bfefe0693d8cd2201f91c5dcadbda4951d402 Mon Sep 17 00:00:00 2001 From: NikitaMic <91180248+NikitaMic@users.noreply.github.com> Date: Fri, 21 Jul 2023 15:37:23 +0200 Subject: [PATCH 137/160] toIntArray changes --- .../org/projectforge/business/poll/PollDO.kt | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 1fdc256157..e77f44915a 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -104,18 +104,9 @@ open class PollDO : DefaultBaseDO() { if (accessUserIds?.contains(currentUserId) == true) { assignmentList.add(PollAssignment.ACCESS) } - - if (!this.fullAccessUserIds.isNullOrBlank()) { - val accessUserIds = toIntArray(this.fullAccessUserIds) - if (accessUserIds?.contains(currentUserId) == true) { - assignmentList.add(PollAssignment.ACCESS) - } - } - if (!this.attendeeIds.isNullOrBlank()) { - val attendeeUserIds = toIntArray(this.attendeeIds) - if (attendeeUserIds?.contains(currentUserId) == true) { - assignmentList.add(PollAssignment.ATTENDEE) - } + val attendeeUserIds = toIntArray(this.attendeeIds) + if (attendeeUserIds?.contains(currentUserId) == true) { + assignmentList.add(PollAssignment.ATTENDEE) } if (assignmentList.isEmpty()) assignmentList.add(PollAssignment.OTHER) From 3558c08621a6b4a7a8dfd35b3abc6c826a467c88 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Mon, 24 Jul 2023 11:04:11 +0200 Subject: [PATCH 138/160] fix restliche npe --- .../kotlin/org/projectforge/business/poll/PollDO.kt | 2 +- .../kotlin/org/projectforge/business/poll/PollDao.kt | 12 +++++------- .../org/projectforge/rest/poll/PollCronJobs.kt | 4 ++-- .../org/projectforge/rest/poll/PollPageRest.kt | 2 +- .../org/projectforge/rest/poll/excel/ExcelExport.kt | 3 +-- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index e77f44915a..f6e138a33d 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -128,7 +128,7 @@ open class PollDO : DefaultBaseDO() { } companion object { - internal fun toIntArray(str: String?): IntArray? { + fun toIntArray(str: String?): IntArray? { if (str.isNullOrBlank()) return null return StringHelper.splitToInts(str, ",", false) } diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index 844be2ed69..9e25c150ef 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -65,12 +65,12 @@ open class PollDao : BaseDao(PollDO::class.java) { fun hasFullAccess(obj: PollDO): Boolean { val loggedInUser = user - if (!obj.fullAccessUserIds.isNullOrBlank() && obj.fullAccessUserIds!!.contains(loggedInUser?.id.toString())) + if (PollDO.toIntArray(obj.fullAccessGroupIds)?.contains(loggedInUser!!.id) == false) return true if (obj.owner?.id == loggedInUser?.id) return true if (!obj.fullAccessGroupIds.isNullOrBlank()) { - val groupIdArray = obj.fullAccessGroupIds!!.split(", ").map { it.toInt() }.toIntArray() + val groupIdArray = PollDO.toIntArray(obj.fullAccessGroupIds) val groupUsers = groupService.getGroupUsers(groupIdArray) if (groupUsers?.contains(loggedInUser) == true) return true @@ -78,10 +78,8 @@ open class PollDao : BaseDao(PollDO::class.java) { return false } - fun isAttendee(obj: PollDO, user: Int?): Boolean { - if (!obj.attendeeIds.isNullOrBlank() && obj.attendeeIds!!.split(", ").contains(user.toString()) - ) - return true - return false + fun isAttendee(obj: PollDO, user: Int): Boolean { + return PollDO.toIntArray(obj.attendeeIds)?.contains(user) == true + } } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt index f91b306f58..09d8f3a1b4 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt @@ -135,11 +135,11 @@ class PollCronJobs { private fun cronDeletePolls() { val polls = pollDao.internalLoadAll() val pollsMoreThanOneYearPast = polls.filter { - it.deadline!!.isBefore(LocalDate.now().minusYears(1)) + it.deadline?.isBefore(LocalDate.now().minusYears(1)) == true } pollsMoreThanOneYearPast.forEach { poll -> val pollResponses = pollResponseDao.internalLoadAll().filter { response -> - response.poll!!.id == poll.id + response.poll?.id == poll.id } pollResponses.forEach { pollResponseDao.internalMarkAsDeleted(it) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 2906f4b42c..3d6c459848 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -373,7 +373,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. question.answers = mutableListOf("yes", "no") } - dto.inputFields!!.add(question) + dto.inputFields?.add(question) dto.owner = userService.getUser(dto.owner?.id) return ResponseEntity.ok( ResponseAction(targetType = TargetType.UPDATE).addVariable("data", dto) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt index 923adcfb4b..d1a69e6fa9 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt @@ -30,13 +30,13 @@ import org.apache.poi.ss.usermodel.CellStyle import org.apache.poi.ss.usermodel.HorizontalAlignment import org.apache.poi.ss.util.CellRangeAddress import org.projectforge.business.group.service.GroupService +import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollResponseDao import org.projectforge.business.user.service.UserService import org.projectforge.rest.dto.User import org.projectforge.rest.poll.Poll import org.projectforge.rest.poll.types.BaseType import org.projectforge.rest.poll.types.PollResponse -import org.projectforge.web.rest.converter.PFUserDOConverter import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired @@ -89,7 +89,6 @@ class ExcelExport { setNewRows(excelSheet, poll, user, res, index) } - var fullAccessUser = poll.fullAccessUsers?.toMutableList() ?: mutableListOf() val accesGroupIds = poll.fullAccessGroups?.filter { it.id != null }?.map { it.id!! }?.toIntArray() val accessUserIds = UserService().getUserIds(groupService.getGroupUsers(accesGroupIds)) From 5638c6144677b34b3dffa2c1ad25aa8a0b24fc9b Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Mon, 24 Jul 2023 11:30:56 +0200 Subject: [PATCH 139/160] fix npe after merge --- .../src/main/kotlin/org/projectforge/business/poll/PollDao.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index 315e77afcf..bc3d65d68a 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -75,8 +75,8 @@ open class PollDao : BaseDao(PollDO::class.java) { return true if (!obj.fullAccessGroupIds.isNullOrBlank()) { val groupIdArray = obj.fullAccessGroupIds!!.split(", ").map { it.toInt() }.toIntArray() - val groupUsers = groupService?.getGroupUsers(groupIdArray) - groupUsers!!.map { it.id }.forEach { + val groupUsers = groupService.getGroupUsers(groupIdArray) + groupUsers.map { it.id }.forEach { if (it == loggedInUserId) return true } From a6383a9503fd3404f73b25c51ef253fc1eac20d5 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Mon, 24 Jul 2023 12:29:50 +0200 Subject: [PATCH 140/160] remove Poll date --- .../org/projectforge/business/poll/PollDO.kt | 5 +-- .../migrate/common/V7.5.1.2__7.5.1.0-POLL.sql | 31 +++++++++---------- .../kotlin/org/projectforge/rest/poll/Poll.kt | 1 - .../projectforge/rest/poll/PollPageRest.kt | 7 ----- 4 files changed, 16 insertions(+), 28 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index dbc6b03e50..a4583ba053 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -64,10 +64,7 @@ open class PollDO : DefaultBaseDO() { @PropertyInfo(i18nKey = "poll.deadline") @get:Column(name = "deadline", nullable = false) open var deadline: LocalDate? = null - - @PropertyInfo(i18nKey = "poll.date") - @get:Column(name = "date") - open var date: LocalDate? = null + @PropertyInfo(i18nKey = "poll.attendees") @get:Column(name = "attendeeIds", nullable = true) diff --git a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql index 9dfe517580..a704b6b02d 100644 --- a/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql +++ b/projectforge-business/src/main/resources/flyway/migrate/common/V7.5.1.2__7.5.1.0-POLL.sql @@ -2,22 +2,21 @@ CREATE TABLE T_POLL ( - pk INTEGER NOT NULL, - deleted BOOLEAN NOT NULL, - created TIMESTAMP WITHOUT TIME ZONE, - last_update TIMESTAMP WITHOUT TIME ZONE, - title CHARACTER VARYING(1000) NOT NULL, - description CHARACTER VARYING(1000), - location CHARACTER VARYING(1000), - owner_fk INTEGER NOT NULL, - deadline DATE NOT NULL, - date DATE, - state CHARACTER VARYING(1000) NOT NULL, - attendeeIds VARCHAR(5000), - groupAttendeeIds VARCHAR(5000), - full_access_user_ids CHARACTER VARYING(255), + pk INTEGER NOT NULL, + deleted BOOLEAN NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE, + last_update TIMESTAMP WITHOUT TIME ZONE, + title CHARACTER VARYING(1000) NOT NULL, + description CHARACTER VARYING(1000), + location CHARACTER VARYING(1000), + owner_fk INTEGER NOT NULL, + deadline DATE NOT NULL, + state CHARACTER VARYING(1000) NOT NULL, + attendeeIds VARCHAR(5000), + groupAttendeeIds VARCHAR(5000), + full_access_user_ids CHARACTER VARYING(255), full_access_group_ids CHARACTER VARYING(255), - inputFields CHARACTER Varying(100000) + inputFields CHARACTER Varying(100000) ); ALTER TABLE T_POLL @@ -32,7 +31,7 @@ CREATE TABLE T_POLL_RESPONSE created TIMESTAMP WITHOUT TIME ZONE, last_update TIMESTAMP WITHOUT TIME ZONE, poll_fk INTEGER NOT NULL, - owner_fk INTEGER NOT NULL, + owner_fk INTEGER NOT NULL, responses CHARACTER Varying(10000) ); diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt index 8eeb5f0d15..671b336cf5 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/Poll.kt @@ -37,7 +37,6 @@ class Poll( var description: String? = null, var owner: PFUserDO? = null, var location: String? = null, - var date: LocalDate? = null, var deadline: LocalDate? = null, var state: PollDO.State? = PollDO.State.RUNNING, var questionType: String? = null, diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index d795830058..3d038259c6 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -635,13 +635,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dataType = UIDataType.STRING ) ) - .add( - UIReadOnlyField( - value = (pollDto.date?.toString() ?: ""), - label = "date", - dataType = UIDataType.STRING - ) - ) .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = "owner", dataType = UIDataType.STRING)) } } From e225db25752b8315ed5cf58df5e34161bebdef02 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Mon, 24 Jul 2023 16:00:30 +0200 Subject: [PATCH 141/160] remove data --- .../src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 3d038259c6..f037309ad6 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -622,7 +622,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. fieldset .add(lc, "title", "description", "location") .add(UISelect.createUserSelect(lc, "owner", false, "poll.owner")) - .add(lc, "deadline", "date") + .add(lc, "deadline") } else { fieldset .add(UIReadOnlyField(value = pollDto.title, label = "titel", dataType = UIDataType.STRING)) From 561e7f92df365d1eba81f612f4f3a49d007e0922 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Mon, 24 Jul 2023 16:06:42 +0200 Subject: [PATCH 142/160] move var to val --- .../main/kotlin/org/projectforge/business/poll/PollDao.kt | 2 +- .../kotlin/org/projectforge/rest/poll/ResponsePageRest.kt | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index bc3d65d68a..93b31bcd3e 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -63,7 +63,7 @@ open class PollDao : BaseDao(PollDO::class.java) { return false } - //returns true if current user hast full access, otherwise returns false + //returns true if current user has full access, otherwise returns false fun hasFullAccess(obj: PollDO): Boolean { val loggedInUserId = ThreadLocalUserContext.userId!! if (!obj.fullAccessUserIds.isNullOrBlank()) { diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt index 1939ea6f82..84301e5213 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt @@ -309,12 +309,12 @@ class ResponsePageRest : AbstractDynamicPageRest() { fun showDelegatedUser( request: HttpServletRequest ): ResponseEntity? { - var attendees = listOfNotNull( + val attendees = listOfNotNull( pollDao.internalGetById(pollId).attendeeIds, pollDao.internalGetById(pollId).fullAccessUserIds, pollDao.internalGetById(pollId).owner?.id ) - var joinedAttendeeIds = attendees.joinToString(", ") + val joinedAttendeeIds = attendees.joinToString(", ") if (questionOwnerId == ThreadLocalUserContext.userId) { return ResponseEntity.ok( ResponseAction() @@ -333,7 +333,8 @@ class ResponsePageRest : AbstractDynamicPageRest() { url = "/react/poll", targetType = TargetType.REDIRECT, message = ResponseAction.Message("poll.exception.noAttendee") - )) + ) + ) } } From 2456d4216f80d44dbc43cc1aaed6653e787ddde6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 25 Jul 2023 13:30:37 +0200 Subject: [PATCH 143/160] Fixed bug --- .../src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 36d1c1f75a..e3daf58eb1 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -653,7 +653,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val pollDO = PollDO() pollDto.copyTo(pollDO) - return if (!pollDao.hasFullAccess(pollDO, ThreadLocalUserContext.user!!)) { + return if (!pollDao.hasFullAccess(pollDO)) { // no full access user UILayout.UserAccess(insert = false, update = false, delete = false, history = false) } else { From 6ef4154d7b056dc15204824eb6c6e9641f011d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 25 Jul 2023 14:12:11 +0200 Subject: [PATCH 144/160] Removed date input and renamed template button --- .../src/main/resources/I18nResources.properties | 2 +- .../src/main/resources/I18nResources_de.properties | 2 +- .../org/projectforge/rest/poll/PollPageRest.kt | 14 +++----------- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index 11ae7365ee..ec0e27677a 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -1976,7 +1976,7 @@ poll.attendee=Attendee poll.attendees=Attendees poll.button.addQuestion=Add own question poll.button.finish=Finish Poll -poll.button.micromataTemplate=Use Micromata Template +poll.button.template=Use Template poll.confirmation.creation=Do you really want to create the poll? You won't be able to edit the questions afterwards.\ Also make sure to add attendees for your poll. poll.confirmation.deleteAnswer=Do you really want to delete this answer? diff --git a/projectforge-business/src/main/resources/I18nResources_de.properties b/projectforge-business/src/main/resources/I18nResources_de.properties index 75684f23df..4815cb973b 100644 --- a/projectforge-business/src/main/resources/I18nResources_de.properties +++ b/projectforge-business/src/main/resources/I18nResources_de.properties @@ -2074,7 +2074,7 @@ poll.attendee=Teilnehmer:in poll.attendees=Teilnehmer:innen poll.button.addQuestion=Eigene Frage hinzuf�gen poll.button.finish=Umfrage beenden -poll.button.micromataTemplate=Micromata Template verwenden +poll.button.template=Vorlage verwenden poll.confirmation.creation=M�chtest du die Umfrage wirklich erstellen? Du kannst die Fragen danach nicht mehr bearbeiten.\ Stelle ebenfalls sicher, dass du Teilnehmer f�r deine Umfrage hinzugef�gt hast. poll.confirmation.deleteAnswer=M�chtest du diese Antwort wirklich l�schen? diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index e3daf58eb1..b6c936e7d4 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -40,7 +40,6 @@ import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.framework.persistence.api.QueryFilter import org.projectforge.framework.persistence.api.impl.CustomResultFilter import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext -import org.projectforge.framework.persistence.user.entities.PFUserDO import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType import org.projectforge.rest.config.Rest @@ -252,12 +251,12 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. UICol(UILength(xs = 3, sm = 3, md = 3, lg = 3)) .add( UIButton.createDefaultButton( - id = "micromata-template-button", + id = "template-button", responseAction = ResponseAction( "${Rest.URL}/poll/addPremadeQuestions", targetType = TargetType.PUT ), - title = "poll.button.micromataTemplate", + title = "poll.button.template", default = false ) ) @@ -621,7 +620,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. fieldset .add(lc, "title", "description", "location") .add(UISelect.createUserSelect(lc, "owner", false, "poll.owner")) - .add(lc, "deadline", "date") + .add(lc, "deadline") } else { fieldset .add(UIReadOnlyField(value = pollDto.title, label = "titel", dataType = UIDataType.STRING)) @@ -634,13 +633,6 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. dataType = UIDataType.STRING ) ) - .add( - UIReadOnlyField( - value = (pollDto.date?.toString() ?: ""), - label = "date", - dataType = UIDataType.STRING - ) - ) .add(UIReadOnlyField(value = pollDto.owner?.displayName, label = "owner", dataType = UIDataType.STRING)) } } From f394690410bfa2d0d071ec376b6b193aa6e43caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 25 Jul 2023 14:17:42 +0200 Subject: [PATCH 145/160] removed date from list --- .../src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index b6c936e7d4..e1f53a85d0 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -138,7 +138,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. val pollLC = LayoutContext(lc) layout.add( UITable.createUIResultSetTable() - .add(pollLC, "title", "description", "location", "owner", "deadline", "date", "state") + .add(pollLC, "title", "description", "location", "owner", "deadline", "state") ) } From b351a99f1d9142e7fe9f59393c862939a72f1c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 25 Jul 2023 14:35:37 +0200 Subject: [PATCH 146/160] Unused import --- .../main/kotlin/org/projectforge/business/poll/PollDao.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index 4f73875866..6927ca7860 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -27,7 +27,6 @@ import org.projectforge.business.group.service.GroupService import org.projectforge.framework.access.OperationType import org.projectforge.framework.persistence.api.BaseDao import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext -import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext.user import org.projectforge.framework.persistence.user.entities.PFUserDO import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Repository @@ -53,8 +52,8 @@ open class PollDao : BaseDao(PollDO::class.java) { if (obj == null && operationType == OperationType.SELECT) { return true }; - if (obj != null && operationType == OperationType.SELECT){ - if(hasFullAccess(obj) || isAttendee(obj, ThreadLocalUserContext.user?.id!!)) + if (obj != null && operationType == OperationType.SELECT) { + if (hasFullAccess(obj) || isAttendee(obj, ThreadLocalUserContext.user?.id!!)) return true } if (obj != null) { From 92286c55796f7614802bc0e2e0da786ff17673fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 25 Jul 2023 14:53:56 +0200 Subject: [PATCH 147/160] Added error stacktrace --- .../main/kotlin/org/projectforge/rest/poll/PollMailService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt index 0108f6d82c..b3da91e1cf 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollMailService.kt @@ -69,7 +69,7 @@ class PollMailService { log.error("There are missing parameters for sending mail: from: $from, to: $to, subject: $subject, content: $content") } } catch (e: Exception) { - log.error(e.toString()) + log.error(e.message, e) } } From 713c8d4cec8777e88634a4222bd89c5524dd1729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 25 Jul 2023 15:16:49 +0200 Subject: [PATCH 148/160] Changed texts on PollInfoPageRest --- .../main/resources/I18nResources.properties | 5 ++ .../resources/I18nResources_de.properties | 5 ++ .../rest/poll/PollInfoPageRest.kt | 50 ++++++------------- site/features.adoc | 2 +- 4 files changed, 27 insertions(+), 35 deletions(-) diff --git a/projectforge-business/src/main/resources/I18nResources.properties b/projectforge-business/src/main/resources/I18nResources.properties index ec0e27677a..4bece7d625 100644 --- a/projectforge-business/src/main/resources/I18nResources.properties +++ b/projectforge-business/src/main/resources/I18nResources.properties @@ -2044,6 +2044,11 @@ poll.title.edit=Edit Poll poll.title.list=Polls poll.userDelegation=User delegation poll.yourAnswers=Your answers +poll.manual.title=Guide to Creating a Poll | First, you need to fill in the main information of the poll. +poll.manual.questions=Next, create the questions for the poll. The questions can be of various types. +poll.manual.singleResponse=A question where you can choose one answer. For example, for a simple Yes or No question. +poll.manual.multiResponse=A question where you can choose multiple answers. Well-suited for a date availability survey. +poll.manual.textQuestion=A question where you can answer with free text. Great for informal feedback. projectmanagement.personDays=Person days projectmanagement.personDays.short=pd question.deleteQuestion=Do your really want to delete this object finally? diff --git a/projectforge-business/src/main/resources/I18nResources_de.properties b/projectforge-business/src/main/resources/I18nResources_de.properties index 4815cb973b..c618f7b54a 100644 --- a/projectforge-business/src/main/resources/I18nResources_de.properties +++ b/projectforge-business/src/main/resources/I18nResources_de.properties @@ -2144,6 +2144,11 @@ poll.title.edit=Umfrage bearbeiten poll.title.list=Umfragen poll.userDelegation=F�r andere Nutzer abstimmen poll.yourAnswers=Deine Antworten +poll.manual.title=Anleitung, um eine Umfrage zu erstellen | Als Erstes muss man die Hauptinformationen der Umfrage ausf�llen. +poll.manual.questions=Anschließend werden die Fragen der Umfrage angelegt. Die Fragen können aus verschiedenen Typen bestehen. +poll.manual.singleResponse=Eine Frage, die bei der man eine Antwort auswählen kann. Beispielsweise für eine simple Ja oder Nein Frage. +poll.manual.multiResponse=Eine Frage, die bei der man eine Antwort auswählen kann. Gut geeignet für eine Datumsumfrage für Verf�gbarkeiten. +poll.manual.textQuestion=Eine Frage, die bei der man mit Freitext antworten kann. Gut geeignet für formloses Feedback. projectmanagement.personDays=Personentage projectmanagement.personDays.short=PT question.deleteQuestion=Soll das Objekt wirklich unwiderruflich gelöscht werden? diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt index 9763846438..e2e2584707 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt @@ -43,41 +43,34 @@ class PollInfoPageRest : AbstractDynamicPageRest() { val layout = UILayout("poll.infopage") val field = UIFieldset() .add( - UILabel( - """ Anleitung, um eine Umfrage zu erstellen - | Als erstes werden die Parameter einer Umfrage angelegt. - """.trimMargin() - ) + UILabel("poll.manual.title") ) field.add( UICol() - .add(UIReadOnlyField("title", label = "title", value = "Test")) + .add(UIReadOnlyField("title", label = "title", value = "poll.title")) ) field.add( UICol() - .add(UIReadOnlyField("description", label = "description", value = "description")) + .add(UIReadOnlyField("description", label = "description", value = "poll.description")) ) field.add( UICol() - .add(UIReadOnlyField("location", label = "location", value = "location")) + .add(UIReadOnlyField("location", label = "location", value = "poll.location")) ) field.add( UICol() - .add(UIReadOnlyField("owner", label = "owner", value = "owner")) + .add(UIReadOnlyField("owner", label = "owner", value = "poll.owner")) ) field.add( UICol() - .add(UIReadOnlyField("deadline", label = "deadline", value = "deadline")) + .add(UIReadOnlyField("deadline", label = "deadline", value = "poll.deadline")) ) field.add( UIRow().add( UICol().add( - UILabel( - """Anschließend werden die Fragen der Umfrage angelegt. - Die Fragen können aus verschiedenen Typen bestehen. """ - ) + UILabel("poll.manual.questions") ) ) ) @@ -85,53 +78,42 @@ class PollInfoPageRest : AbstractDynamicPageRest() { layout.add(field) layout.add( - UIFieldset().add(UILabel("YesNoQuestion")).add( + UIFieldset().add(UILabel("Single Response poll.question")).add( UICol() .add( UIReadOnlyField( "question", - label = "Question", - value = "Eine Frage, die mit Ja oder Nein beantwortet werden kann." + label = "poll.question", + value = "poll.manual.singleResponse" ) ) ) ) layout.add( - UIFieldset().add(UILabel("MultipleChoiceQuestion")).add( + UIFieldset().add(UILabel("Multiple Choice poll.question")).add( UICol() .add( UIReadOnlyField( "question", - label = "Question", - value = "Eine Frage, die mit mehreren Antworten beantwortet werden kann." + label = "poll.question", + value = "poll.manual.multiResponse" ) ) ) ) layout.add( - UIFieldset().add(UILabel("TextQuestion")).add( + UIFieldset().add(UILabel("Text poll.question")).add( UICol() .add( UIReadOnlyField( "question", - label = "Question", - value = "Eine Frage, die mit einer Freitext Antwort beantwortet werden kann." + label = "poll.question", + value = "poll.manual.textQuestion" ) ) ) ) - layout.add( - UIFieldset().add(UILabel("Dropdown")).add( - UICol() - .add( - UIReadOnlyField( - "question", label = "Question", - value = "Eine Frage, die mit einem Dropdown beantwortet werden kann." - ) - ) - ) - ) LayoutUtils.process(layout) diff --git a/site/features.adoc b/site/features.adoc index 29e7db2efa..ab00ca9f40 100644 --- a/site/features.adoc +++ b/site/features.adoc @@ -87,6 +87,6 @@ Your users only need a modern Internet browser. There is no need to install anyt == Technical requirements -- The ProjectForge server is vailable as a ready-to-run executable jar (for Windows, Mac, Linux and all Java-capable platforms). +- The ProjectForge server is available as a ready-to-run executable jar (for Windows, Mac, Linux and all Java-capable platforms). - ProjectForge is available as docker image. - The users only need a modern web browser to have full access to ProjectForge anywhere. From fd00cb410cf3fa67d4cdaca23d8ac8908cb04537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Tue, 25 Jul 2023 15:27:05 +0200 Subject: [PATCH 149/160] Added translate --- .../rest/poll/PollInfoPageRest.kt | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt index e2e2584707..251143011b 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollInfoPageRest.kt @@ -23,6 +23,7 @@ package org.projectforge.rest.poll +import org.projectforge.framework.i18n.translate import org.projectforge.rest.config.Rest import org.projectforge.rest.core.AbstractDynamicPageRest import org.projectforge.rest.dto.FormLayoutData @@ -48,23 +49,23 @@ class PollInfoPageRest : AbstractDynamicPageRest() { field.add( UICol() - .add(UIReadOnlyField("title", label = "title", value = "poll.title")) + .add(UIReadOnlyField("title", label = "title", value = translate("poll.title"))) ) field.add( UICol() - .add(UIReadOnlyField("description", label = "description", value = "poll.description")) + .add(UIReadOnlyField("description", label = "description", value = translate("poll.description"))) ) field.add( UICol() - .add(UIReadOnlyField("location", label = "location", value = "poll.location")) + .add(UIReadOnlyField("location", label = "location", value = translate("poll.location"))) ) field.add( UICol() - .add(UIReadOnlyField("owner", label = "owner", value = "poll.owner")) + .add(UIReadOnlyField("owner", label = "owner", value = translate("poll.owner"))) ) field.add( UICol() - .add(UIReadOnlyField("deadline", label = "deadline", value = "poll.deadline")) + .add(UIReadOnlyField("deadline", label = "deadline", value = translate("poll.deadline"))) ) field.add( @@ -78,37 +79,37 @@ class PollInfoPageRest : AbstractDynamicPageRest() { layout.add(field) layout.add( - UIFieldset().add(UILabel("Single Response poll.question")).add( + UIFieldset().add(UILabel("Single Response " + translate("poll.question"))).add( UICol() .add( UIReadOnlyField( "question", label = "poll.question", - value = "poll.manual.singleResponse" + value = translate("poll.manual.singleResponse") ) ) ) ) layout.add( - UIFieldset().add(UILabel("Multiple Choice poll.question")).add( + UIFieldset().add(UILabel("Multiple Response " + translate("poll.question"))).add( UICol() .add( UIReadOnlyField( "question", label = "poll.question", - value = "poll.manual.multiResponse" + value = translate("poll.manual.multiResponse") ) ) ) ) layout.add( - UIFieldset().add(UILabel("Text poll.question")).add( + UIFieldset().add(UILabel("Text " + translate("poll.question"))).add( UICol() .add( UIReadOnlyField( "question", label = "poll.question", - value = "poll.manual.textQuestion" + value = translate("poll.manual.textQuestion") ) ) From d5613d5997caeeadeb8db48f05167c8c52f76226 Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 26 Jul 2023 10:02:12 +0200 Subject: [PATCH 150/160] fix margen at excel export --- .../kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt index 65c4b655f0..134cce79bd 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt @@ -139,7 +139,8 @@ class ExcelExport { var merge = 1 poll.inputFields?.forEach { question -> - if (question.type == BaseType.MultiResponseQuestion || question.type == BaseType.SingleResponseQuestion) { + var ansers = question.answers ?: mutableListOf("") + if ((question.type == BaseType.MultiResponseQuestion || question.type == BaseType.SingleResponseQuestion) && ansers.size >= 2) { var counter = merge question.answers?.forEach { answer -> excelRow1.getCell(counter).setCellValue(answer) From 3ce4bef75e20f1936a3b79d8ecec3a34e92afe5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Wed, 26 Jul 2023 10:39:15 +0200 Subject: [PATCH 151/160] Added check to require two answers --- .../org/projectforge/business/poll/PollDao.kt | 1 - .../projectforge/rest/poll/PollPageRest.kt | 35 +++++++++++-------- .../rest/poll/excel/ExcelExport.kt | 13 +++---- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index c062c5b4f0..2c5b53bf1d 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -85,6 +85,5 @@ open class PollDao : BaseDao(PollDO::class.java) { fun isAttendee(obj: PollDO, user: Int): Boolean { return PollDO.toIntArray(obj.attendeeIds)?.contains(user) == true - } } \ No newline at end of file diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 7384250686..4478ce9517 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -459,7 +459,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. index, field.uid, answerIndex, - layout + layout, + field.answers!!.size ) ) .add(UISpacer()) @@ -489,7 +490,8 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. inputFieldIndex: Int, questionUid: String?, answerIndex: Int, - layout: UILayout + layout: UILayout, + answerAmount: Int, ): UIRow { val row = UIRow() row.add( @@ -503,19 +505,22 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. ) ) if (!objGiven) { - row.add( - UICol() - .add(UISpacer()) - .add( - UIButton.createDangerButton( - id = "X", - responseAction = ResponseAction( - "${Rest.URL}/poll/deleteAnswer/${questionUid}/${answerIndex}", - targetType = TargetType.POST - ) - ).withConfirmMessage(layout, confirmMessage = "poll.confirmation.deleteAnswer") - ) - ) + // require at least two answers + if (answerAmount > 2) { + row.add( + UICol() + .add(UISpacer()) + .add( + UIButton.createDangerButton( + id = "X", + responseAction = ResponseAction( + "${Rest.URL}/poll/deleteAnswer/${questionUid}/${answerIndex}", + targetType = TargetType.POST + ) + ).withConfirmMessage(layout, confirmMessage = "poll.confirmation.deleteAnswer") + ) + ) + } } return row diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt index 65c4b655f0..970abff9c1 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt @@ -30,7 +30,6 @@ import org.apache.poi.ss.usermodel.CellStyle import org.apache.poi.ss.usermodel.HorizontalAlignment import org.apache.poi.ss.util.CellRangeAddress import org.projectforge.business.group.service.GroupService -import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollResponseDao import org.projectforge.business.user.service.UserService import org.projectforge.rest.dto.User @@ -88,9 +87,9 @@ class ExcelExport { setNewRows(excelSheet, poll, user, res, index + FIRST_DATA_ROW_NUM) } - var fullAccessUser = poll.fullAccessUsers?.toMutableList() ?: mutableListOf() - val accesGroupIds = poll.fullAccessGroups?.filter { it.id != null }?.map { it.id!! }?.toIntArray() - val accessUserIds = UserService().getUserIds(groupService.getGroupUsers(accesGroupIds)) + val fullAccessUser = poll.fullAccessUsers?.toMutableList() ?: mutableListOf() + val accessGroupIds = poll.fullAccessGroups?.filter { it.id != null }?.map { it.id!! }?.toIntArray() + val accessUserIds = UserService().getUserIds(groupService.getGroupUsers(accessGroupIds)) val accessUsers = User.toUserList(accessUserIds) User.restoreDisplayNames(accessUsers, userService) @@ -106,16 +105,15 @@ class ExcelExport { } User.restoreDisplayNames(fullAccessUser, userService) - fullAccessUser.forEachIndexed { index, user -> + fullAccessUser.forEachIndexed { _, user -> var number = (anzNewRows) if (poll.attendees?.map { it.id }?.contains(user.id) == false) { val res = PollResponse() responses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } // User add a Response if (res.id != null) { - // Put Data's in the Row + // Put data in the Row setNewRows(excelSheet, poll, user, res, number) - number++ } } } @@ -209,7 +207,6 @@ class ExcelExport { counterOfOverlength += pufferSplit[i].length / 70 } excelRow.setHeight((14 + counterOfOverlength * 14 + counterOfBreaking * 14).toFloat()) - //excelRow.setHeight(20F) ///TODO LEON FIX THIS PROBLEM } private fun countLines(str: String): Int { From cb08d769503ad57b6f65378e54e843e5e5557fcb Mon Sep 17 00:00:00 2001 From: Jonas Bernst Date: Wed, 26 Jul 2023 10:41:15 +0200 Subject: [PATCH 152/160] fix export --- .../kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt index 134cce79bd..04850ed210 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt @@ -140,7 +140,7 @@ class ExcelExport { var merge = 1 poll.inputFields?.forEach { question -> var ansers = question.answers ?: mutableListOf("") - if ((question.type == BaseType.MultiResponseQuestion || question.type == BaseType.SingleResponseQuestion) && ansers.size >= 2) { + if (question.type == BaseType.MultiResponseQuestion || question.type == BaseType.SingleResponseQuestion) { var counter = merge question.answers?.forEach { answer -> excelRow1.getCell(counter).setCellValue(answer) @@ -151,7 +151,9 @@ class ExcelExport { excelRow.getCell(merge).setCellValue(question.question) excelSheet.autosize(merge) counter-- - excelSheet.addMergeRegion(CellRangeAddress(0, 0, merge, counter)) + if (ansers.size >= 2) { + excelSheet.addMergeRegion(CellRangeAddress(0, 0, merge, counter)) + } merge = counter } else { excelRow.getCell(merge).setCellValue(question.question) From 83e1d04ce674a35ae19bee5b08fd467063ab23ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Wed, 26 Jul 2023 11:06:12 +0200 Subject: [PATCH 153/160] Fixed wrong if statement --- .../src/main/kotlin/org/projectforge/business/poll/PollDao.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index c062c5b4f0..b56a81b409 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -66,7 +66,7 @@ open class PollDao : BaseDao(PollDO::class.java) { fun hasFullAccess(obj: PollDO): Boolean { val loggedInUserId = ThreadLocalUserContext.userId!! if (!obj.fullAccessUserIds.isNullOrBlank()) { - val userIdArray = obj.fullAccessGroupIds!!.split(", ").map { it.toInt() }.toIntArray() + val userIdArray = obj.fullAccessUserIds!!.split(", ").map { it.toInt() }.toIntArray() if (userIdArray.contains(loggedInUserId)) return true } From 61f6ff352cb08c7c1737bda4bcb05b6fc752bf3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Wed, 26 Jul 2023 11:08:30 +0200 Subject: [PATCH 154/160] fix spelling --- .../kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt index 04850ed210..ecb30d70a4 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt @@ -30,7 +30,6 @@ import org.apache.poi.ss.usermodel.CellStyle import org.apache.poi.ss.usermodel.HorizontalAlignment import org.apache.poi.ss.util.CellRangeAddress import org.projectforge.business.group.service.GroupService -import org.projectforge.business.poll.PollDO import org.projectforge.business.poll.PollResponseDao import org.projectforge.business.user.service.UserService import org.projectforge.rest.dto.User @@ -139,7 +138,7 @@ class ExcelExport { var merge = 1 poll.inputFields?.forEach { question -> - var ansers = question.answers ?: mutableListOf("") + var answers = question.answers ?: mutableListOf("") if (question.type == BaseType.MultiResponseQuestion || question.type == BaseType.SingleResponseQuestion) { var counter = merge question.answers?.forEach { answer -> @@ -151,7 +150,7 @@ class ExcelExport { excelRow.getCell(merge).setCellValue(question.question) excelSheet.autosize(merge) counter-- - if (ansers.size >= 2) { + if (answers.size >= 2) { excelSheet.addMergeRegion(CellRangeAddress(0, 0, merge, counter)) } merge = counter From 1818ed5e786950600688cd4a91d8a4b439944b65 Mon Sep 17 00:00:00 2001 From: Jona Fleckenstein Date: Wed, 26 Jul 2023 14:37:57 +0200 Subject: [PATCH 155/160] Remove test cron --- .../src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt index 09d8f3a1b4..0bf7118b22 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt @@ -61,8 +61,7 @@ class PollCronJobs { * Cron job for daily stuff */ - /*@Scheduled(cron = "0 0 1 * * *") // 1am everyday*/ - @Scheduled(cron = "0 * * * * *") // every minute for testing + @Scheduled(cron = "0 0 1 * * *") // 1am everyday fun dailyCronJobs() { log.info("Start daily cron jobs") cronDeletePolls() From b894417871111929e8b66f122dfa37d8bc32d812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Wed, 26 Jul 2023 15:27:36 +0200 Subject: [PATCH 156/160] Added State.FINISHED_AND_MAIL_SENT and smaller fixes --- .../org/projectforge/business/poll/PollDO.kt | 4 +- .../org/projectforge/business/poll/PollDao.kt | 13 ++-- .../projectforge/rest/poll/PollCronJobs.kt | 59 +++++++++++-------- .../rest/poll/excel/ExcelExport.kt | 8 ++- 4 files changed, 48 insertions(+), 36 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 73ea091ea6..36ef8803de 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -64,7 +64,7 @@ open class PollDO : DefaultBaseDO() { @PropertyInfo(i18nKey = "poll.deadline") @get:Column(name = "deadline", nullable = false) open var deadline: LocalDate? = null - + @PropertyInfo(i18nKey = "poll.attendees") @get:Column(name = "attendeeIds", nullable = true) @@ -121,7 +121,7 @@ open class PollDO : DefaultBaseDO() { } enum class State { - RUNNING, FINISHED + RUNNING, FINISHED, FINISHED_AND_MAIL_SENT } companion object { diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt index 786e66e9fd..611b59ed1f 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDao.kt @@ -66,18 +66,21 @@ open class PollDao : BaseDao(PollDO::class.java) { fun hasFullAccess(obj: PollDO): Boolean { val loggedInUserId = ThreadLocalUserContext.userId!! if (!obj.fullAccessUserIds.isNullOrBlank()) { - val userIdArray = obj.fullAccessUserIds!!.split(", ").map { it.toInt() }.toIntArray() - if (userIdArray.contains(loggedInUserId)) + val userIdArray = PollDO.toIntArray(obj.fullAccessUserIds) + if (userIdArray?.contains(loggedInUserId) == true) { return true + } } - if (obj.owner?.id == loggedInUserId) + if (obj.owner?.id == loggedInUserId) { return true + } if (!obj.fullAccessGroupIds.isNullOrBlank()) { val groupIdArray = PollDO.toIntArray(obj.fullAccessGroupIds) val groupUsers = groupService.getGroupUsers(groupIdArray) - groupUsers.map { it.id }.forEach { - if (it == loggedInUserId) + groupUsers.map { it.id }.forEach { id -> + if (id == loggedInUserId) { return true + } } } return false diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt index 0bf7118b22..61513bfa30 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollCronJobs.kt @@ -38,7 +38,6 @@ import java.time.LocalDate import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit -import java.util.* @RestController class PollCronJobs { @@ -75,33 +74,41 @@ class PollCronJobs { val pollDOs = pollDao.internalLoadAll() // set State.FINISHED for all old polls and export excel pollDOs.forEach { pollDO -> - if (pollDO.deadline?.isBefore(LocalDate.now()) == true && pollDO.state != PollDO.State.FINISHED) { - pollDO.state = PollDO.State.FINISHED - - val poll = Poll() - poll.copyFrom(pollDO) - - val excel = exporter.getExcel(poll) - - val mailAttachment = object : MailAttachment { - override fun getFilename(): String { - return "${pollDO.title}_${LocalDateTime.now().year}_Result.xlsx" - } - - override fun getContent(): ByteArray? { - return excel + // try to send mail until successfully changed to FINISHED_AND_MAIL_SENT + if (pollDO.state != PollDO.State.FINISHED_AND_MAIL_SENT) { + if (pollDO.deadline?.isBefore(LocalDate.now()) == true) { + pollDO.state = PollDO.State.FINISHED + + try { + val poll = Poll() + poll.copyFrom(pollDO) + + val excel = exporter.getExcel(poll) + + val mailAttachment = object : MailAttachment { + override fun getFilename(): String { + return "${pollDO.title}_${LocalDateTime.now().year}_Result.xlsx" + } + + override fun getContent(): ByteArray? { + return excel + } + } + // add all attendees mails + val mailTo: ArrayList = + ArrayList(poll.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) + val mailFrom = pollDO.owner?.email.toString() + val mailSubject = translateMsg("poll.mail.ended.subject") + val mailContent = translateMsg("poll.mail.ended.content", pollDO.title, pollDO.owner?.displayName) + + pollDao.internalSaveOrUpdate(pollDO) + log.info("Set state of poll (${pollDO.id}) ${pollDO.title} to FINISHED") + pollMailService.sendMail(mailFrom, mailTo, mailContent, mailSubject, listOf(mailAttachment)) + pollDO.state = PollDO.State.FINISHED_AND_MAIL_SENT + } catch (e: Exception) { + log.error(e.message, e) } } - // add all attendees mails - val mailTo: ArrayList = - ArrayList(poll.attendees?.map { it.email }?.mapNotNull { it } ?: emptyList()) - val mailFrom = pollDO.owner?.email.toString() - val mailSubject = translateMsg("poll.mail.ended.subject") - val mailContent = translateMsg("poll.mail.ended.content", pollDO.title, pollDO.owner?.displayName) - - pollDao.internalSaveOrUpdate(pollDO) - log.info("Set state of poll (${pollDO.id}) ${pollDO.title} to FINISHED") - pollMailService.sendMail(mailFrom, mailTo, mailContent, mailSubject, listOf(mailAttachment)) } } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt index a4a5d8f0c0..62b9773b39 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt @@ -137,7 +137,7 @@ class ExcelExport { var merge = 1 poll.inputFields?.forEach { question -> - var answers = question.answers ?: mutableListOf("") + val answers = question.answers if (question.type == BaseType.MultiResponseQuestion || question.type == BaseType.SingleResponseQuestion) { var counter = merge question.answers?.forEach { answer -> @@ -149,8 +149,10 @@ class ExcelExport { excelRow.getCell(merge).setCellValue(question.question) excelSheet.autosize(merge) counter-- - if (answers.size >= 2) { - excelSheet.addMergeRegion(CellRangeAddress(0, 0, merge, counter)) + answers?.size?.let { + if (it >= 2) { + excelSheet.addMergeRegion(CellRangeAddress(0, 0, merge, counter)) + } } merge = counter } else { From 361f04cb749a4c5d54eb7695c0f82298543785da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Wed, 9 Aug 2023 10:14:59 +0200 Subject: [PATCH 157/160] Renamed PollResponsePageRest --- .../kotlin/org/projectforge/rest/poll/PollPageRest.kt | 2 +- .../poll/{ResponsePageRest.kt => PollResponsePageRest.kt} | 8 ++++---- .../org/projectforge/rest/poll/types/PollResponse.kt | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) rename projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/{ResponsePageRest.kt => PollResponsePageRest.kt} (98%) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt index 4478ce9517..0258998323 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollPageRest.kt @@ -129,7 +129,7 @@ class PollPageRest : AbstractDTOPagesRest(PollDao::class. * @return the response page. */ override fun getStandardEditPage(): String { - return "${PagesResolver.getDynamicPageUrl(ResponsePageRest::class.java)}?pollId=:id" + return "${PagesResolver.getDynamicPageUrl(PollResponsePageRest::class.java)}?pollId=:id" } override fun createListLayout( diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponsePageRest.kt similarity index 98% rename from projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt rename to projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponsePageRest.kt index f3b62804d1..47f35614d3 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/ResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponsePageRest.kt @@ -57,7 +57,7 @@ import javax.validation.Valid @RestController @RequestMapping("${Rest.URL}/response") -class ResponsePageRest : AbstractDynamicPageRest() { +class PollResponsePageRest : AbstractDynamicPageRest() { @Autowired private lateinit var pollDao: PollDao @@ -158,9 +158,9 @@ class ResponsePageRest : AbstractDynamicPageRest() { val pollResponse = PollResponse() pollResponse.poll = pollData - pollResponseDao.internalLoadAll().firstOrNull { response -> - response.owner?.id == questionOwnerId - && response.poll?.id == pollData.id + pollResponseDao.internalLoadAll().firstOrNull { pollResponse -> + pollResponse.owner?.id == questionOwnerId + && pollResponse.poll?.id == pollData.id }?.let { pollResponse.copyFrom(it) } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt index 39cd28a03c..8a1a756b2d 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt @@ -32,10 +32,10 @@ import org.projectforge.rest.dto.BaseDTO class PollResponse : BaseDTO() { var poll: PollDO? = null var owner: PFUserDO? = null - var responses: MutableList? = mutableListOf() + var pollResponses: MutableList? = mutableListOf() override fun copyTo(dest: PollResponseDO) { - if (!this.responses.isNullOrEmpty()) { - dest.responses = ObjectMapper().writeValueAsString(this.responses) + if (!this.pollResponses.isNullOrEmpty()) { + dest.responses = ObjectMapper().writeValueAsString(this.pollResponses) } super.copyTo(dest) } @@ -43,7 +43,7 @@ class PollResponse : BaseDTO() { override fun copyFrom(src: PollResponseDO) { if (!src.responses.isNullOrEmpty()) { val a = ObjectMapper().readValue(src.responses, MutableList::class.java) - this.responses = a.map { QuestionAnswer().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() + this.pollResponses = a.map { QuestionAnswer().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() } super.copyFrom(src) } From 33c024ad35e83493edbc07ed232e82f5f43af235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Wed, 9 Aug 2023 10:21:33 +0200 Subject: [PATCH 158/160] Renamed variables --- .../rest/poll/PollResponsePageRest.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponsePageRest.kt index 47f35614d3..11777e4d53 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponsePageRest.kt @@ -170,10 +170,10 @@ class PollResponsePageRest : AbstractDynamicPageRest() { val questionAnswer = QuestionAnswer() questionAnswer.uid = UUID.randomUUID().toString() questionAnswer.questionUid = field.uid - pollResponse.responses?.firstOrNull { + pollResponse.pollResponses?.firstOrNull { it.questionUid == field.uid }.let { - if (it == null) pollResponse.responses?.add(questionAnswer) + if (it == null) pollResponse.pollResponses?.add(questionAnswer) } val col = UICol() @@ -182,7 +182,7 @@ class PollResponsePageRest : AbstractDynamicPageRest() { col.add( PollPageRest.getUiElement( pollDto.isFinished(), - "responses[$index].answers[0]", + "pollResponses[$index].answers[0]", "poll.question.textQuestion", UIDataType.STRING ) @@ -191,8 +191,8 @@ class PollResponsePageRest : AbstractDynamicPageRest() { if (field.type == BaseType.MultiResponseQuestion || field.type === BaseType.SingleResponseQuestion) { field.answers?.forEachIndexed { index2, _ -> - if (pollResponse.responses?.get(index)?.answers?.getOrNull(index2) == null) { - pollResponse.responses?.get(index)?.answers?.add(index2, false) + if (pollResponse.pollResponses?.get(index)?.answers?.getOrNull(index2) == null) { + pollResponse.pollResponses?.get(index)?.answers?.add(index2, false) } if (field.type == BaseType.MultiResponseQuestion) { col.add( @@ -235,12 +235,12 @@ class PollResponsePageRest : AbstractDynamicPageRest() { if (!pollDto.isFinished()) { layout.add( UIButton.createDefaultButton( - id = "addResponse", + id = "addPollResonse", title = translateMsg("poll.respond"), responseAction = ResponseAction( RestResolver.getRestUrl( this::class.java, - "addResponse" + "addPollResonse" ) + "/?questionOwner=${questionOwnerId}", targetType = TargetType.POST ) ) @@ -252,8 +252,8 @@ class PollResponsePageRest : AbstractDynamicPageRest() { return FormLayoutData(pollResponse, layout, createServerData(request)) } - @PostMapping("addResponse") - fun addResponse( + @PostMapping("addPollResonse") + fun addPollResonse( request: HttpServletRequest, @RequestBody postData: PostData, @RequestParam("questionOwner") questionOwner: Int? ): ResponseEntity? { @@ -265,7 +265,7 @@ class PollResponsePageRest : AbstractDynamicPageRest() { pollResponse.owner?.id == questionOwner && pollResponse.poll?.id == postData.data.poll?.id }?.let { - it.responses = pollResponseDO.responses + it.pollResponses = pollResponseDO.pollResponses pollResponseDao.update(it) return ResponseEntity.ok( ResponseAction( From 5e79fc72bcb348c2174d1a091d60ef8520e9d92b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Wed, 9 Aug 2023 13:38:30 +0200 Subject: [PATCH 159/160] Renamed response --- .../org/projectforge/business/poll/PollDO.kt | 4 +++- .../business/poll/PollResponseDO.kt | 6 +++--- .../business/poll/filter/PollState.kt | 2 +- .../rest/poll/PollResponsePageRest.kt | 2 +- .../rest/poll/excel/ExcelExport.kt | 20 +++++++++---------- .../rest/poll/types/PollResponse.kt | 6 +++--- 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt index 36ef8803de..76f4e412c6 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollDO.kt @@ -115,8 +115,10 @@ open class PollDO : DefaultBaseDO() { fun getPollStatus(): PollState { return if (this.state == State.FINISHED) { PollState.FINISHED - } else { + } else if (this.state == State.RUNNING) { PollState.RUNNING + } else { + PollState.FINISHED_AND_MAIL_SENT } } diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt index 23f105a7a0..1b2b3120ca 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt @@ -48,7 +48,7 @@ open class PollResponseDO : DefaultBaseDO() { @get:JoinColumn(name = "owner_fk", nullable = false) open var owner: PFUserDO? = null - @PropertyInfo(i18nKey = "poll.responses") - @get:Column(name = "responses", nullable = true, length = 1000) - open var responses: String? = null + @PropertyInfo(i18nKey = "poll.pollResponses") + @get:Column(name = "pollResponses", nullable = true, length = 1000) + open var pollResponses: String? = null } diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollState.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollState.kt index 393ab9fa4c..220e660070 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollState.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/filter/PollState.kt @@ -26,7 +26,7 @@ package org.projectforge.business.poll.filter import org.projectforge.common.i18n.I18nEnum enum class PollState(val key: String) : I18nEnum { - RUNNING("running"), FINISHED("finished"); + RUNNING("running"), FINISHED("finished"), FINISHED_AND_MAIL_SENT("finished and mail sent"); override val i18nKey: String get() = "poll.$key" diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponsePageRest.kt index 11777e4d53..dbe811f0a3 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponsePageRest.kt @@ -56,7 +56,7 @@ import javax.validation.Valid @RestController -@RequestMapping("${Rest.URL}/response") +@RequestMapping("${Rest.URL}/pollResponse") class PollResponsePageRest : AbstractDynamicPageRest() { @Autowired diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt index 62b9773b39..e7b7535ba7 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt @@ -62,7 +62,7 @@ class ExcelExport { fun getExcel(poll: Poll): ByteArray? { - val responses = pollResponseDao.internalLoadAll().filter { it.poll?.id == poll.id } + val pollResponses = pollResponseDao.internalLoadAll().filter { it.poll?.id == poll.id } val classPathResource = ClassPathResource("officeTemplates/PollResultTemplate" + ".xlsx") try { @@ -83,7 +83,7 @@ class ExcelExport { poll.attendees?.sortedBy { it.displayName } poll.attendees?.forEachIndexed { index, user -> val res = PollResponse() - responses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } + pollResponses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } setNewRows(excelSheet, poll, user, res, index + FIRST_DATA_ROW_NUM) } @@ -109,7 +109,7 @@ class ExcelExport { var number = (anzNewRows) if (poll.attendees?.map { it.id }?.contains(user.id) == false) { val res = PollResponse() - responses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } + pollResponses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } // User add a Response if (res.id != null) { // Put data in the Row @@ -175,25 +175,25 @@ class ExcelExport { var largestAnswer = "" poll.inputFields?.forEachIndexed { _, question -> - val questionAnswer = res?.responses?.find { it.questionUid == question.uid } + val questionAnswer = res?.pollResponses?.find { it.questionUid == question.uid } if (questionAnswer?.answers.isNullOrEmpty()) { cell += question.answers?.size ?: 0 } - questionAnswer?.answers?.forEachIndexed { ind, antwort -> + questionAnswer?.answers?.forEachIndexed { ind, answer -> cell++ if (question.type == BaseType.MultiResponseQuestion) { - if (antwort is Boolean && antwort == true) { + if (answer is Boolean && answer == true) { excelRow.getCell(cell).setCellValue("X") } } else if (question.type == BaseType.SingleResponseQuestion) { - if (antwort is String && antwort.equals(question.answers?.get(ind))) { + if (answer is String && answer.equals(question.answers?.get(ind))) { excelRow.getCell(cell).setCellValue("X") } } else { - excelRow.getCell(cell).setCellValue(antwort.toString()) - if (countLines(antwort.toString()) > countLines(largestAnswer)) { - largestAnswer = antwort.toString() + excelRow.getCell(cell).setCellValue(answer.toString()) + if (countLines(answer.toString()) > countLines(largestAnswer)) { + largestAnswer = answer.toString() } } excelSheet.autosize(cell) diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt index 8a1a756b2d..ab1020ceae 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt @@ -35,14 +35,14 @@ class PollResponse : BaseDTO() { var pollResponses: MutableList? = mutableListOf() override fun copyTo(dest: PollResponseDO) { if (!this.pollResponses.isNullOrEmpty()) { - dest.responses = ObjectMapper().writeValueAsString(this.pollResponses) + dest.pollResponses = ObjectMapper().writeValueAsString(this.pollResponses) } super.copyTo(dest) } override fun copyFrom(src: PollResponseDO) { - if (!src.responses.isNullOrEmpty()) { - val a = ObjectMapper().readValue(src.responses, MutableList::class.java) + if (!src.pollResponses.isNullOrEmpty()) { + val a = ObjectMapper().readValue(src.pollResponses, MutableList::class.java) this.pollResponses = a.map { QuestionAnswer().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() } super.copyFrom(src) From 58ca047c759783bbb42c712090887067df4c1289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81on=20Spohr?= Date: Thu, 10 Aug 2023 08:47:08 +0200 Subject: [PATCH 160/160] Reverted naming --- .../business/poll/PollResponseDO.kt | 6 ++--- .../rest/poll/PollResponsePageRest.kt | 26 +++++++++---------- .../rest/poll/excel/ExcelExport.kt | 8 +++--- .../rest/poll/types/PollResponse.kt | 12 ++++----- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt index 1b2b3120ca..23f105a7a0 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/poll/PollResponseDO.kt @@ -48,7 +48,7 @@ open class PollResponseDO : DefaultBaseDO() { @get:JoinColumn(name = "owner_fk", nullable = false) open var owner: PFUserDO? = null - @PropertyInfo(i18nKey = "poll.pollResponses") - @get:Column(name = "pollResponses", nullable = true, length = 1000) - open var pollResponses: String? = null + @PropertyInfo(i18nKey = "poll.responses") + @get:Column(name = "responses", nullable = true, length = 1000) + open var responses: String? = null } diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponsePageRest.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponsePageRest.kt index dbe811f0a3..1e2a81ef24 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponsePageRest.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/PollResponsePageRest.kt @@ -158,9 +158,9 @@ class PollResponsePageRest : AbstractDynamicPageRest() { val pollResponse = PollResponse() pollResponse.poll = pollData - pollResponseDao.internalLoadAll().firstOrNull { pollResponse -> - pollResponse.owner?.id == questionOwnerId - && pollResponse.poll?.id == pollData.id + pollResponseDao.internalLoadAll().firstOrNull { response -> + response.owner?.id == questionOwnerId + && response.poll?.id == pollData.id }?.let { pollResponse.copyFrom(it) } @@ -170,10 +170,10 @@ class PollResponsePageRest : AbstractDynamicPageRest() { val questionAnswer = QuestionAnswer() questionAnswer.uid = UUID.randomUUID().toString() questionAnswer.questionUid = field.uid - pollResponse.pollResponses?.firstOrNull { + pollResponse.responses?.firstOrNull { it.questionUid == field.uid }.let { - if (it == null) pollResponse.pollResponses?.add(questionAnswer) + if (it == null) pollResponse.responses?.add(questionAnswer) } val col = UICol() @@ -182,7 +182,7 @@ class PollResponsePageRest : AbstractDynamicPageRest() { col.add( PollPageRest.getUiElement( pollDto.isFinished(), - "pollResponses[$index].answers[0]", + "responses[$index].answers[0]", "poll.question.textQuestion", UIDataType.STRING ) @@ -191,8 +191,8 @@ class PollResponsePageRest : AbstractDynamicPageRest() { if (field.type == BaseType.MultiResponseQuestion || field.type === BaseType.SingleResponseQuestion) { field.answers?.forEachIndexed { index2, _ -> - if (pollResponse.pollResponses?.get(index)?.answers?.getOrNull(index2) == null) { - pollResponse.pollResponses?.get(index)?.answers?.add(index2, false) + if (pollResponse.responses?.get(index)?.answers?.getOrNull(index2) == null) { + pollResponse.responses?.get(index)?.answers?.add(index2, false) } if (field.type == BaseType.MultiResponseQuestion) { col.add( @@ -235,12 +235,12 @@ class PollResponsePageRest : AbstractDynamicPageRest() { if (!pollDto.isFinished()) { layout.add( UIButton.createDefaultButton( - id = "addPollResonse", + id = "addResponse", title = translateMsg("poll.respond"), responseAction = ResponseAction( RestResolver.getRestUrl( this::class.java, - "addPollResonse" + "addResponse" ) + "/?questionOwner=${questionOwnerId}", targetType = TargetType.POST ) ) @@ -252,8 +252,8 @@ class PollResponsePageRest : AbstractDynamicPageRest() { return FormLayoutData(pollResponse, layout, createServerData(request)) } - @PostMapping("addPollResonse") - fun addPollResonse( + @PostMapping("addResponse") + fun addResponse( request: HttpServletRequest, @RequestBody postData: PostData, @RequestParam("questionOwner") questionOwner: Int? ): ResponseEntity? { @@ -265,7 +265,7 @@ class PollResponsePageRest : AbstractDynamicPageRest() { pollResponse.owner?.id == questionOwner && pollResponse.poll?.id == postData.data.poll?.id }?.let { - it.pollResponses = pollResponseDO.pollResponses + it.responses = pollResponseDO.responses pollResponseDao.update(it) return ResponseEntity.ok( ResponseAction( diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt index e7b7535ba7..31e14ef09d 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/excel/ExcelExport.kt @@ -62,7 +62,7 @@ class ExcelExport { fun getExcel(poll: Poll): ByteArray? { - val pollResponses = pollResponseDao.internalLoadAll().filter { it.poll?.id == poll.id } + val responses = pollResponseDao.internalLoadAll().filter { it.poll?.id == poll.id } val classPathResource = ClassPathResource("officeTemplates/PollResultTemplate" + ".xlsx") try { @@ -83,7 +83,7 @@ class ExcelExport { poll.attendees?.sortedBy { it.displayName } poll.attendees?.forEachIndexed { index, user -> val res = PollResponse() - pollResponses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } + responses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } setNewRows(excelSheet, poll, user, res, index + FIRST_DATA_ROW_NUM) } @@ -109,7 +109,7 @@ class ExcelExport { var number = (anzNewRows) if (poll.attendees?.map { it.id }?.contains(user.id) == false) { val res = PollResponse() - pollResponses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } + responses.find { it.owner?.id == user.id }?.let { res.copyFrom(it) } // User add a Response if (res.id != null) { // Put data in the Row @@ -175,7 +175,7 @@ class ExcelExport { var largestAnswer = "" poll.inputFields?.forEachIndexed { _, question -> - val questionAnswer = res?.pollResponses?.find { it.questionUid == question.uid } + val questionAnswer = res?.responses?.find { it.questionUid == question.uid } if (questionAnswer?.answers.isNullOrEmpty()) { cell += question.answers?.size ?: 0 diff --git a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt index ab1020ceae..39cd28a03c 100644 --- a/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt +++ b/projectforge-rest/src/main/kotlin/org/projectforge/rest/poll/types/PollResponse.kt @@ -32,18 +32,18 @@ import org.projectforge.rest.dto.BaseDTO class PollResponse : BaseDTO() { var poll: PollDO? = null var owner: PFUserDO? = null - var pollResponses: MutableList? = mutableListOf() + var responses: MutableList? = mutableListOf() override fun copyTo(dest: PollResponseDO) { - if (!this.pollResponses.isNullOrEmpty()) { - dest.pollResponses = ObjectMapper().writeValueAsString(this.pollResponses) + if (!this.responses.isNullOrEmpty()) { + dest.responses = ObjectMapper().writeValueAsString(this.responses) } super.copyTo(dest) } override fun copyFrom(src: PollResponseDO) { - if (!src.pollResponses.isNullOrEmpty()) { - val a = ObjectMapper().readValue(src.pollResponses, MutableList::class.java) - this.pollResponses = a.map { QuestionAnswer().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() + if (!src.responses.isNullOrEmpty()) { + val a = ObjectMapper().readValue(src.responses, MutableList::class.java) + this.responses = a.map { QuestionAnswer().toObject(ObjectMapper().writeValueAsString(it)) }.toMutableList() } super.copyFrom(src) }