From eefd10c51f6ea7a71fa4ef981946c8c073f6bd7c Mon Sep 17 00:00:00 2001 From: Wayne Date: Mon, 6 May 2024 18:38:24 +0900 Subject: [PATCH] Add leave api --- .../tsms/modules/timesheet/entity/Leave.kt | 8 +++ .../timesheet/entity/LeaveRepository.kt | 5 ++ .../timesheet/entity/LeaveTypeRepository.kt | 6 ++ .../modules/timesheet/entity/Timesheet.kt | 3 + .../modules/timesheet/service/LeaveService.kt | 72 +++++++++++++++++++ .../timesheet/service/TimesheetsService.kt | 3 +- .../timesheet/web/TimesheetsController.kt | 29 ++++++-- .../timesheet/web/models/LeaveEntry.kt | 7 ++ src/main/resources/application.yml | 1 + .../20240506_01_wayne/01_leave_type_data.sql | 11 +++ .../20240506_01_wayne/02_timesheet_remark.sql | 5 ++ .../20240506_01_wayne/03_leave_update.sql | 4 ++ 12 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/ffii/tsms/modules/timesheet/entity/LeaveTypeRepository.kt create mode 100644 src/main/java/com/ffii/tsms/modules/timesheet/service/LeaveService.kt create mode 100644 src/main/java/com/ffii/tsms/modules/timesheet/web/models/LeaveEntry.kt create mode 100644 src/main/resources/db/changelog/changes/20240506_01_wayne/01_leave_type_data.sql create mode 100644 src/main/resources/db/changelog/changes/20240506_01_wayne/02_timesheet_remark.sql create mode 100644 src/main/resources/db/changelog/changes/20240506_01_wayne/03_leave_update.sql diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/entity/Leave.kt b/src/main/java/com/ffii/tsms/modules/timesheet/entity/Leave.kt index 4594e72..b91b24b 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/entity/Leave.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/entity/Leave.kt @@ -4,6 +4,7 @@ import com.ffii.core.entity.BaseEntity import com.ffii.tsms.modules.data.entity.Staff import jakarta.persistence.* import jakarta.validation.constraints.NotNull +import java.time.LocalDate @Entity @Table(name = "leave") @@ -20,4 +21,11 @@ open class Leave : BaseEntity() { @ManyToOne @JoinColumn(name = "leaveTypeId") open var leaveType: LeaveType? = null + + @NotNull + @Column(name = "recordDate") + open var recordDate: LocalDate? = null + + @Column(name = "remark") + open var remark: String? = null } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/entity/LeaveRepository.kt b/src/main/java/com/ffii/tsms/modules/timesheet/entity/LeaveRepository.kt index fc5bdf7..7c5a2de 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/entity/LeaveRepository.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/entity/LeaveRepository.kt @@ -1,6 +1,11 @@ package com.ffii.tsms.modules.timesheet.entity; import com.ffii.core.support.AbstractRepository +import com.ffii.tsms.modules.data.entity.Staff +import java.time.LocalDate interface LeaveRepository : AbstractRepository { + fun findAllByStaff(staff: Staff): List + + fun deleteAllByStaffAndRecordDate(staff: Staff, recordDate: LocalDate) } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/entity/LeaveTypeRepository.kt b/src/main/java/com/ffii/tsms/modules/timesheet/entity/LeaveTypeRepository.kt new file mode 100644 index 0000000..4f01893 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/timesheet/entity/LeaveTypeRepository.kt @@ -0,0 +1,6 @@ +package com.ffii.tsms.modules.timesheet.entity; + +import com.ffii.core.support.AbstractRepository + +interface LeaveTypeRepository : AbstractRepository { +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/entity/Timesheet.kt b/src/main/java/com/ffii/tsms/modules/timesheet/entity/Timesheet.kt index 87058bf..9128493 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/entity/Timesheet.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/entity/Timesheet.kt @@ -28,4 +28,7 @@ open class Timesheet : BaseEntity() { @ManyToOne @JoinColumn(name = "projectTaskId") open var projectTask: ProjectTask? = null + + @Column(name = "remark") + open var remark: String? = null } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/service/LeaveService.kt b/src/main/java/com/ffii/tsms/modules/timesheet/service/LeaveService.kt new file mode 100644 index 0000000..474953d --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/timesheet/service/LeaveService.kt @@ -0,0 +1,72 @@ +package com.ffii.tsms.modules.timesheet.service + +import com.ffii.core.exception.BadRequestException +import com.ffii.tsms.modules.data.service.StaffsService +import com.ffii.tsms.modules.timesheet.entity.* +import com.ffii.tsms.modules.timesheet.web.models.LeaveEntry +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +@Service +open class LeaveService( + private val leaveRepository: LeaveRepository, + private val leaveTypeRepository: LeaveTypeRepository, + private val staffsService: StaffsService +) { + open fun getLeaveTypes(): List { + return leaveTypeRepository.findAll() + } + + open fun getLeaves(): Map> { + val currentStaff = staffsService.currentStaff() ?: return emptyMap() + return transformToLeaveEntryMap(leaveRepository.findAllByStaff(currentStaff)) + } + + @Transactional + open fun saveLeave(recordLeaveEntry: Map>): Map> { + // Need to be associated with a staff + val currentStaff = staffsService.currentStaff() ?: throw BadRequestException() + + val leaveTypesMap = getLeaveTypes().associateBy { it.id } + + val leaves = recordLeaveEntry.entries.flatMap { (entryDate, leaveEntries) -> + // Replace db leave entries by deleting and then adding back + leaveRepository.deleteAllByStaffAndRecordDate(currentStaff, entryDate) + + mergeLeaveEntriesByType(leaveEntries).map { leaveEntry -> + Leave().apply { + this.staff = currentStaff + this.recordDate = entryDate + this.leaveType = leaveTypesMap[leaveEntry.leaveTypeId] + this.leaveHours = leaveEntry.inputHours + } + } + } + + val savedLeaves = leaveRepository.saveAll(leaves) + + return transformToLeaveEntryMap(savedLeaves) + } + + private fun transformToLeaveEntryMap(leaves: List): Map> { + return leaves + .groupBy { leave -> leave.recordDate!!.format(DateTimeFormatter.ISO_LOCAL_DATE) } + .mapValues { (_, leaveEntries) -> leaveEntries.map { leave -> + LeaveEntry( + id = leave.id!!, + inputHours = leave.leaveHours ?: 0.0, + leaveTypeId = leave.leaveType!!.id + ) + } } + } + + private fun mergeLeaveEntriesByType(entries: List): List { + return entries + .groupBy { leaveEntry -> leaveEntry.leaveTypeId } + .values.map { leaveEntires -> + leaveEntires.reduce { acc, leaveEntry -> acc.copy(inputHours = acc.inputHours + leaveEntry.inputHours) } + } + } +} diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt b/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt index dddef98..65245e8 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt @@ -16,7 +16,6 @@ import kotlin.jvm.optionals.getOrDefault import kotlin.jvm.optionals.getOrNull @Service - open class TimesheetsService( private val timesheetRepository: TimesheetRepository, private val projectTaskRepository: ProjectTaskRepository, @@ -54,7 +53,7 @@ open class TimesheetsService( open fun getTimesheet(): Map> { // Need to be associated with a staff - val currentStaff = staffsService.currentStaff() ?: throw BadRequestException() + val currentStaff = staffsService.currentStaff() ?: return emptyMap() return transformToTimeEntryMap(timesheetRepository.findAllByStaff(currentStaff)) } diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt b/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt index 6d86e80..02f8f31 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt @@ -1,7 +1,10 @@ package com.ffii.tsms.modules.timesheet.web import com.ffii.core.exception.BadRequestException +import com.ffii.tsms.modules.timesheet.entity.LeaveType +import com.ffii.tsms.modules.timesheet.service.LeaveService import com.ffii.tsms.modules.timesheet.service.TimesheetsService +import com.ffii.tsms.modules.timesheet.web.models.LeaveEntry import com.ffii.tsms.modules.timesheet.web.models.TimeEntry import jakarta.validation.Valid import org.springframework.web.bind.annotation.GetMapping @@ -14,21 +17,39 @@ import java.time.format.DateTimeFormatter @RestController @RequestMapping("/timesheets") -class TimesheetsController(private val timesheetsService: TimesheetsService) { +class TimesheetsController(private val timesheetsService: TimesheetsService, private val leaveService: LeaveService) { @PostMapping("/save") fun newTimesheetEntry(@Valid @RequestBody recordTimesheet: Map>): Map> { - val parseDateTimeResult = kotlin.runCatching { + val parsedEntries = kotlin.runCatching { recordTimesheet.mapKeys { (dateString) -> LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE) } - } - val parsedEntries = parseDateTimeResult.getOrElse { throw BadRequestException() } + }.getOrElse { throw BadRequestException() } return timesheetsService.saveTimesheet(parsedEntries) } + @PostMapping("/saveLeave") + fun newLeaveEntry(@Valid @RequestBody recordLeave: Map>): Map> { + val parsedEntries = kotlin.runCatching { + recordLeave.mapKeys { (dateString) -> LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE) } + }.getOrElse { throw BadRequestException() } + + return leaveService.saveLeave(parsedEntries) + } + @GetMapping fun getTimesheetEntry(): Map> { return timesheetsService.getTimesheet() } + + @GetMapping("/leaves") + fun getLeaveEntry(): Map> { + return leaveService.getLeaves() + } + + @GetMapping("/leaveTypes") + fun leaveTypes(): List { + return leaveService.getLeaveTypes() + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/web/models/LeaveEntry.kt b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/LeaveEntry.kt new file mode 100644 index 0000000..315e1a1 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/LeaveEntry.kt @@ -0,0 +1,7 @@ +package com.ffii.tsms.modules.timesheet.web.models + +data class LeaveEntry( + val id: Long, + val leaveTypeId: Long?, + val inputHours: Double +) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8fd5034..10c241d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -21,6 +21,7 @@ spring: database-platform: org.hibernate.dialect.MySQL8Dialect properties: hibernate: + globally_quoted_identifiers: true dialect: storage_engine: innodb diff --git a/src/main/resources/db/changelog/changes/20240506_01_wayne/01_leave_type_data.sql b/src/main/resources/db/changelog/changes/20240506_01_wayne/01_leave_type_data.sql new file mode 100644 index 0000000..782806c --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240506_01_wayne/01_leave_type_data.sql @@ -0,0 +1,11 @@ +-- liquibase formatted sql +-- changeset wayne:leave_type + +INSERT +INTO + leave_type + (name) +VALUES + ('Annual Leave'), + ('Sick Leave'), + ('Special Leave'); diff --git a/src/main/resources/db/changelog/changes/20240506_01_wayne/02_timesheet_remark.sql b/src/main/resources/db/changelog/changes/20240506_01_wayne/02_timesheet_remark.sql new file mode 100644 index 0000000..cee6eab --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240506_01_wayne/02_timesheet_remark.sql @@ -0,0 +1,5 @@ +-- liquibase formatted sql +-- changeset wayne:timesheet_remark + +ALTER TABLE timesheet ADD remark VARCHAR(255) NULL; + diff --git a/src/main/resources/db/changelog/changes/20240506_01_wayne/03_leave_update.sql b/src/main/resources/db/changelog/changes/20240506_01_wayne/03_leave_update.sql new file mode 100644 index 0000000..7dafbbc --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240506_01_wayne/03_leave_update.sql @@ -0,0 +1,4 @@ +-- liquibase formatted sql +-- changeset wayne:leave_update + +ALTER TABLE `leave` ADD recordDate date NOT NULL, ADD remark VARCHAR(255) NULL;