Просмотр исходного кода

Add timesheet saving

tags/Baseline_30082024_BACKEND_UAT
Wayne 1 год назад
Родитель
Сommit
b0e498e7c5
13 измененных файлов: 277 добавлений и 0 удалений
  1. +8
    -0
      src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt
  2. +2
    -0
      src/main/java/com/ffii/tsms/modules/project/entity/ProjectTaskRepository.kt
  3. +23
    -0
      src/main/java/com/ffii/tsms/modules/timesheet/entity/Leave.kt
  4. +6
    -0
      src/main/java/com/ffii/tsms/modules/timesheet/entity/LeaveRepository.kt
  5. +15
    -0
      src/main/java/com/ffii/tsms/modules/timesheet/entity/LeaveType.kt
  6. +31
    -0
      src/main/java/com/ffii/tsms/modules/timesheet/entity/Timesheet.kt
  7. +12
    -0
      src/main/java/com/ffii/tsms/modules/timesheet/entity/TimesheetRepository.kt
  8. +82
    -0
      src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt
  9. +34
    -0
      src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt
  10. +10
    -0
      src/main/java/com/ffii/tsms/modules/timesheet/web/models/TimeEntry.kt
  11. +24
    -0
      src/main/resources/db/changelog/changes/20240504_01_wayne/01_timesheet.sql
  12. +8
    -0
      src/main/resources/db/changelog/changes/20240504_01_wayne/02_leave_type.sql
  13. +22
    -0
      src/main/resources/db/changelog/changes/20240504_01_wayne/03_leave.sql

+ 8
- 0
src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt Просмотреть файл

@@ -2,6 +2,7 @@ package com.ffii.tsms.modules.data.service

import com.ffii.core.support.AbstractBaseEntityService
import com.ffii.core.support.JdbcDao
import com.ffii.tsms.modules.common.SecurityUtils
import com.ffii.tsms.modules.data.entity.*
import com.ffii.tsms.modules.data.entity.projections.StaffSearchInfo
import com.ffii.tsms.modules.data.web.models.NewStaffRequest
@@ -13,6 +14,7 @@ import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.util.*
import java.util.stream.Collectors
import kotlin.jvm.optionals.getOrNull


@Service
@@ -225,4 +227,10 @@ open class StaffsService(
)
return jdbcDao.queryForList(sql.toString(), args)
}

open fun currentStaff(): Staff? {
return SecurityUtils.getUser().getOrNull()?.let { user ->
staffRepository.findByUserId(user.id).getOrNull()
}
}
}

+ 2
- 0
src/main/java/com/ffii/tsms/modules/project/entity/ProjectTaskRepository.kt Просмотреть файл

@@ -4,4 +4,6 @@ import com.ffii.core.support.AbstractRepository

interface ProjectTaskRepository : AbstractRepository<ProjectTask, Long> {
fun findAllByProject(project: Project): List<ProjectTask>

fun findByProjectAndTask(project: Project, task: Task): ProjectTask
}

+ 23
- 0
src/main/java/com/ffii/tsms/modules/timesheet/entity/Leave.kt Просмотреть файл

@@ -0,0 +1,23 @@
package com.ffii.tsms.modules.timesheet.entity

import com.ffii.core.entity.BaseEntity
import com.ffii.tsms.modules.data.entity.Staff
import jakarta.persistence.*
import jakarta.validation.constraints.NotNull

@Entity
@Table(name = "leave")
open class Leave : BaseEntity<Long>() {
@Column(name = "leaveHours")
open var leaveHours: Double? = null

@NotNull
@ManyToOne
@JoinColumn(name = "staffId")
open var staff: Staff? = null

@NotNull
@ManyToOne
@JoinColumn(name = "leaveTypeId")
open var leaveType: LeaveType? = null
}

+ 6
- 0
src/main/java/com/ffii/tsms/modules/timesheet/entity/LeaveRepository.kt Просмотреть файл

@@ -0,0 +1,6 @@
package com.ffii.tsms.modules.timesheet.entity;

import com.ffii.core.support.AbstractRepository

interface LeaveRepository : AbstractRepository<Leave, Long> {
}

+ 15
- 0
src/main/java/com/ffii/tsms/modules/timesheet/entity/LeaveType.kt Просмотреть файл

@@ -0,0 +1,15 @@
package com.ffii.tsms.modules.timesheet.entity

import com.ffii.core.entity.IdEntity
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Table
import jakarta.validation.constraints.NotNull

@Entity
@Table(name = "leave_type")
open class LeaveType : IdEntity<Long>() {
@NotNull
@Column(name = "name")
open var name: String? = null
}

+ 31
- 0
src/main/java/com/ffii/tsms/modules/timesheet/entity/Timesheet.kt Просмотреть файл

@@ -0,0 +1,31 @@
package com.ffii.tsms.modules.timesheet.entity

import com.ffii.core.entity.BaseEntity
import com.ffii.tsms.modules.data.entity.Staff
import com.ffii.tsms.modules.project.entity.ProjectTask
import jakarta.persistence.*
import jakarta.validation.constraints.NotNull
import java.time.LocalDate

@Entity
@Table(name = "timesheet")
open class Timesheet : BaseEntity<Long>() {
@Column(name = "normalConsumed")
open var normalConsumed: Double? = null

@Column(name = "otConsumed")
open var otConsumed: Double? = null

@NotNull
@Column(name = "recordDate")
open var recordDate: LocalDate? = null

@NotNull
@ManyToOne
@JoinColumn(name = "staffId")
open var staff: Staff? = null

@ManyToOne
@JoinColumn(name = "projectTaskId")
open var projectTask: ProjectTask? = null
}

+ 12
- 0
src/main/java/com/ffii/tsms/modules/timesheet/entity/TimesheetRepository.kt Просмотреть файл

@@ -0,0 +1,12 @@
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 TimesheetRepository : AbstractRepository<Timesheet, Long> {

fun findAllByStaff(staff: Staff): List<Timesheet>

fun deleteAllByStaffAndRecordDate(staff: Staff, recordDate: LocalDate)
}

+ 82
- 0
src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt Просмотреть файл

@@ -0,0 +1,82 @@
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.project.entity.ProjectRepository
import com.ffii.tsms.modules.project.entity.ProjectTaskRepository
import com.ffii.tsms.modules.project.entity.TaskRepository
import com.ffii.tsms.modules.timesheet.entity.Timesheet
import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository
import com.ffii.tsms.modules.timesheet.web.models.TimeEntry
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import kotlin.jvm.optionals.getOrDefault
import kotlin.jvm.optionals.getOrNull

@Service

open class TimesheetsService(
private val timesheetRepository: TimesheetRepository,
private val projectTaskRepository: ProjectTaskRepository,
private val projectRepository: ProjectRepository,
private val taskRepository: TaskRepository,
private val staffsService: StaffsService
) {
@Transactional
open fun saveTimesheet(recordTimeEntry: Map<LocalDate, List<TimeEntry>>): Map<String, List<TimeEntry>> {
// Need to be associated with a staff
val currentStaff = staffsService.currentStaff() ?: throw BadRequestException()

val timesheetEntries = recordTimeEntry.entries.flatMap { (entryDate, timeEntries) ->
// Replace db timesheet entries by deleting and then adding back
timesheetRepository.deleteAllByStaffAndRecordDate(currentStaff, entryDate)

mergeTimeEntriesByProjectAndTask(timeEntries).map { timeEntry ->
val task = timeEntry.taskId?.let { taskRepository.findById(it).getOrNull() }
val project = timeEntry.projectId?.let { projectRepository.findById(it).getOrNull() }
val projectTask = project?.let { p -> task?.let { t -> projectTaskRepository.findByProjectAndTask(p, t) } }

Timesheet().apply {
this.staff = currentStaff
this.recordDate = entryDate
this.normalConsumed = timeEntry.inputHours
this.projectTask = projectTask
}
}
}

val savedTimesheets = timesheetRepository.saveAll(timesheetEntries)

return transformToTimeEntryMap(savedTimesheets)
}

open fun getTimesheet(): Map<String, List<TimeEntry>> {
// Need to be associated with a staff
val currentStaff = staffsService.currentStaff() ?: throw BadRequestException()
return transformToTimeEntryMap(timesheetRepository.findAllByStaff(currentStaff))
}

private fun transformToTimeEntryMap(timesheets: List<Timesheet>): Map<String, List<TimeEntry>> {
return timesheets
.groupBy { timesheet -> timesheet.recordDate!!.format(DateTimeFormatter.ISO_LOCAL_DATE) }
.mapValues { (_, timesheets) -> timesheets.map { timesheet ->
TimeEntry(
id = timesheet.id!!,
projectId = timesheet.projectTask?.project?.id,
taskId = timesheet.projectTask?.task?.id,
taskGroupId = timesheet.projectTask?.task?.taskGroup?.id,
inputHours = timesheet.normalConsumed ?: 0.0
)
} }
}

private fun mergeTimeEntriesByProjectAndTask(entries: List<TimeEntry>): List<TimeEntry> {
return entries
.groupBy { timeEntry -> Pair(timeEntry.projectId, timeEntry.taskId) }
.values.map { timeEntries ->
timeEntries.reduce { acc, timeEntry -> acc.copy(inputHours = acc.inputHours + timeEntry.inputHours) }
}
}
}

+ 34
- 0
src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt Просмотреть файл

@@ -0,0 +1,34 @@
package com.ffii.tsms.modules.timesheet.web

import com.ffii.core.exception.BadRequestException
import com.ffii.tsms.modules.timesheet.service.TimesheetsService
import com.ffii.tsms.modules.timesheet.web.models.TimeEntry
import jakarta.validation.Valid
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.time.LocalDate
import java.time.format.DateTimeFormatter

@RestController
@RequestMapping("/timesheets")
class TimesheetsController(private val timesheetsService: TimesheetsService) {
@PostMapping("/save")
fun newTimesheetEntry(@Valid @RequestBody recordTimesheet: Map<String, List<TimeEntry>>): Map<String, List<TimeEntry>> {
val parseDateTimeResult = kotlin.runCatching {
recordTimesheet.mapKeys { (dateString) ->
LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE)
}
}
val parsedEntries = parseDateTimeResult.getOrElse { throw BadRequestException() }

return timesheetsService.saveTimesheet(parsedEntries)
}

@GetMapping
fun getTimesheetEntry(): Map<String, List<TimeEntry>> {
return timesheetsService.getTimesheet()
}
}

+ 10
- 0
src/main/java/com/ffii/tsms/modules/timesheet/web/models/TimeEntry.kt Просмотреть файл

@@ -0,0 +1,10 @@
package com.ffii.tsms.modules.timesheet.web.models


data class TimeEntry(
val id: Long,
val projectId: Long?,
val taskGroupId: Long?,
val taskId: Long?,
val inputHours: Double
)

+ 24
- 0
src/main/resources/db/changelog/changes/20240504_01_wayne/01_timesheet.sql Просмотреть файл

@@ -0,0 +1,24 @@
-- liquibase formatted sql
-- changeset wayne:timesheet

DROP TABLE timesheet;

CREATE TABLE timesheet (
id INT NOT NULL AUTO_INCREMENT,
version INT NOT NULL DEFAULT '0',
created datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
createdBy VARCHAR(30) NULL,
modified datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
modifiedBy VARCHAR(30) NULL,
deleted TINYINT(1) NOT NULL DEFAULT '0',
normalConsumed DOUBLE NULL,
otConsumed DOUBLE NULL,
recordDate date NOT NULL,
staffId INT NOT NULL,
projectTaskId INT NULL,
CONSTRAINT pk_timesheet PRIMARY KEY (id)
);

ALTER TABLE timesheet ADD CONSTRAINT FK_TIMESHEET_ON_PROJECTTASKID FOREIGN KEY (projectTaskId) REFERENCES project_task (id);

ALTER TABLE timesheet ADD CONSTRAINT FK_TIMESHEET_ON_STAFFID FOREIGN KEY (staffId) REFERENCES staff (id);

+ 8
- 0
src/main/resources/db/changelog/changes/20240504_01_wayne/02_leave_type.sql Просмотреть файл

@@ -0,0 +1,8 @@
-- liquibase formatted sql
-- changeset wayne:leave_type

CREATE TABLE leave_type (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
CONSTRAINT pk_leave_type PRIMARY KEY (id)
);

+ 22
- 0
src/main/resources/db/changelog/changes/20240504_01_wayne/03_leave.sql Просмотреть файл

@@ -0,0 +1,22 @@
-- liquibase formatted sql
-- changeset wayne:leave

DROP TABLE `leave`;

CREATE TABLE `leave` (
id INT NOT NULL AUTO_INCREMENT,
version INT NOT NULL DEFAULT '0',
created datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
createdBy VARCHAR(30) NULL,
modified datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
modifiedBy VARCHAR(30) NULL,
deleted TINYINT(1) NOT NULL DEFAULT '0',
leaveHours DOUBLE NULL,
staffId INT NOT NULL,
leaveTypeId INT NOT NULL,
CONSTRAINT pk_leave PRIMARY KEY (id)
);

ALTER TABLE `leave` ADD CONSTRAINT FK_LEAVE_ON_LEAVETYPEID FOREIGN KEY (leaveTypeId) REFERENCES leave_type (id);

ALTER TABLE `leave` ADD CONSTRAINT FK_LEAVE_ON_STAFFID FOREIGN KEY (staffId) REFERENCES staff (id);

Загрузка…
Отмена
Сохранить