Kaynağa Gözat

Merge branch 'master' of https://git.2fi-solutions.com/davidhui/TSMS-backend

tags/Baseline_30082024_BACKEND_UAT
MSI\2Fi 1 yıl önce
ebeveyn
işleme
f7f7a8e26a
9 değiştirilmiş dosya ile 123 ekleme ve 49 silme
  1. +37
    -0
      src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt
  2. +3
    -0
      src/main/java/com/ffii/tsms/modules/project/entity/Project.kt
  3. +2
    -0
      src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt
  4. +1
    -0
      src/main/java/com/ffii/tsms/modules/project/web/models/NewProjectRequest.kt
  5. +50
    -47
      src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
  6. +0
    -1
      src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt
  7. +1
    -1
      src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt
  8. +24
    -0
      src/main/resources/db/changelog/changes/20240814_01_cyril/01_update_timesheet_and_task.sql
  9. +5
    -0
      src/main/resources/db/changelog/changes/20240815_01_cyril/01_update_project.sql

+ 37
- 0
src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt Dosyayı Görüntüle

@@ -4,16 +4,21 @@ import com.ffii.core.exception.UnprocessableEntityException
import com.ffii.core.support.AbstractBaseEntityService
import com.ffii.core.support.JdbcDao
import com.ffii.tsms.modules.common.SecurityUtils
import com.ffii.tsms.modules.common.SettingNames
import com.ffii.tsms.modules.common.mail.pojo.MailRequest
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
import com.ffii.tsms.modules.data.web.models.SalaryEffectiveInfo
import com.ffii.tsms.modules.user.entity.User
import com.ffii.tsms.modules.user.entity.UserRepository
import jakarta.mail.internet.InternetAddress
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDate
import java.util.*
import kotlin.jvm.optionals.getOrNull

@@ -138,6 +143,11 @@ open class StaffsService(
name = req.name
phone1 = req.phone1
email = req.email ?: null
locked = if (req.departDate != null) {
LocalDate.now().isAfter(req.departDate)
} else {
false
}
}
)

@@ -226,6 +236,14 @@ open class StaffsService(
this.department = department
}

if (req.departDate != null) {
val user = userRepository.findByUsernameAndDeletedFalse(req.staffId).orElseThrow()
user.apply {
locked = LocalDate.now().isAfter(req.departDate)
}

userRepository.save(user)
}
// salaryEffectiveService.saveSalaryEffective(staff.id!!, salary.salaryPoint.toLong())
salaryEffectiveService.updateSalaryEffective(staff.id!!, req.salaryEffectiveInfo?.sortedBy { it.date }, req.delSalaryEffectiveInfo)

@@ -267,4 +285,23 @@ open class StaffsService(
val latestSalaryPoint = salaryEffectInfo?.maxByOrNull { it.date }?.salaryPoint
return latestSalaryPoint
}

@Scheduled(cron = "0 0 0 * * ?") // Runs at 00:00 AM every day
open fun checkDepartStaffs() {
val staffs = staffRepository.findAll()

val users = mutableListOf<User>()

staffs.forEach { staff ->
val user = userRepository.findByUsernameAndDeletedFalse(staff.staffId).orElseThrow()

user.apply {
locked = LocalDate.now().isAfter(staff.departDate)
}

users += user
}

userRepository.saveAll(users)
}
}

+ 3
- 0
src/main/java/com/ffii/tsms/modules/project/entity/Project.kt Dosyayı Görüntüle

@@ -69,6 +69,9 @@ open class Project : BaseEntity<Long>() {
@Column(name = "totalManhour")
open var totalManhour: Double? = null

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

@OneToMany(mappedBy = "project", cascade = [CascadeType.ALL], orphanRemoval = true)
open var gradeAllocations: MutableSet<GradeAllocation> = mutableSetOf()



+ 2
- 0
src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt Dosyayı Görüntüle

@@ -227,6 +227,7 @@ open class ProjectsService(
expectedTotalFee = request.expectedProjectFee
subContractFee = request.subContractFee
totalManhour = request.totalManhour
ratePerManhour = request.ratePerManhour
actualStart = request.projectActualStart
actualEnd = request.projectActualEnd
status = if (this.status == "Deleted" || this.deleted == true) "Deleted"
@@ -729,6 +730,7 @@ open class ProjectsService(
expectedProjectFee = row.getCell(9).numericCellValue,
subContractFee = null,
totalManhour = row.getCell(11).numericCellValue,
ratePerManhour = 250.0,
locationId = 1, // HK
buildingTypeIds = mutableListOf(buildingType!!.id!!),
workNatureIds = mutableListOf(workNature!!.id!!),


+ 1
- 0
src/main/java/com/ffii/tsms/modules/project/web/models/NewProjectRequest.kt Dosyayı Görüntüle

@@ -41,6 +41,7 @@ data class NewProjectRequest(
val manhourPercentageByGrade: Map<Long, Double>,
val taskGroups: Map<Long, TaskGroupAllocation>,
val allocatedStaffIds: List<Long>,
val ratePerManhour: Double,

// Milestones
val milestones: Map<Long, Milestone>,


+ 50
- 47
src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt Dosyayı Görüntüle

@@ -66,6 +66,7 @@ open class ReportService(
private val COMPLETION_PROJECT = "templates/report/AR05_Project Completion Report.xlsx"
private val CROSS_TEAM_CHARGE_REPORT = "templates/report/Cross Team Charge Report.xlsx"

private val chargeFee = 1.15
private fun conditionalFormattingNegative(sheet: Sheet) {
// Create a conditional formatting rule
val sheetCF = sheet.sheetConditionalFormatting
@@ -778,7 +779,7 @@ open class ReportService(
}

createCell(2).apply {
setCellValue((project.expectedTotalFee ?: 0.0) - (project.subContractFee ?: 0.0))
setCellValue(project.expectedTotalFee ?: 0.0)
cellStyle.dataFormat = accountingStyle
}
}
@@ -2117,46 +2118,31 @@ open class ReportService(

open fun getProjectResourceOverconsumptionReport(args: Map<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder(
"SELECT"
+ " p.code, "
+ " p.name, "
+ " tm.code as team, "
+ " concat(c.code, ' - ',c.name) as client, "
+ " COALESCE(concat(ss.code, ' - ', ss.name), 'N/A') as subsidiary, "
+ " (p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8 as plannedBudget, "
+ " sum(t.consumedBudget) as actualConsumedBudget, "
+ " COALESCE(p.totalManhour, 0) as plannedManhour, "
+ " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as actualConsumedManhour, "
+ " sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) as budgetConsumptionRate, "
+ " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0) as manhourConsumptionRate, "
+ " case "
+ " when sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) >= 1 "
+ " or (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) >= 1 "
+ " then 'Overconsumption' "
+ " when sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) >= :lowerLimit and sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) <= 1 "
+ " or (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) >= :lowerLimit and (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) <= 1 "
+ " then 'Potential Overconsumption' "
+ " else 'Within Budget' "
+ " END as status "
+ " from "
+ " (SELECT t.*, (t.normalConsumed + COALESCE(t.otConsumed, 0)) * sal.hourlyRate as consumedBudget "
+ " FROM "
+ " ( "
+ " SELECT t.id AS tid, max(se.date) AS max_date "
+ " FROM timesheet t "
+ " INNER JOIN salary_effective se ON se.staffId = t.staffId AND se.date <= t.recordDate "
+ " GROUP BY t.id "
+ " ) max_se "
+ " LEFT JOIN timesheet t ON t.id = max_se.tid "
+ " LEFT JOIN staff s on s.id = t.staffId "
+ " inner join salary_effective se on se.date = max_se.max_date "
+ " left join salary sal on se.salaryId = sal.salaryPoint) t "
+ " left join project p on p.id = t.projectId "
+ " left join team tm on p.teamLead = tm.teamLead "
+ " left join customer c on c.id = p.customerId "
+ " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id "
+ " WHERE p.deleted = false "
+ " and p.status = 'On-going' "
" SELECT "
+ " p.code, p.name, tm.code as team, concat(c.code, ' -',c.name) as client, COALESCE(concat(ss.code, ' -', ss.name), 'N/A') as subsidiary "
+ " , (p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8 as plannedBudget, sum(t.consumedBudget) as actualConsumedBudget "
+ " , COALESCE(p.totalManhour, 0) as plannedManhour, sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as actualConsumedManhour "
+ " , sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) as budgetConsumptionRate "
+ " , sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0) as manhourConsumptionRate "
+ " , case when sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) >= 1 "
+ " or (sum(t.normalConsumed + COALESCE(t.otConsumed,0)) / COALESCE(p.totalManhour,0)) >= 1 "
+ " then'Overconsumption' when sum(t.consumedBudget)/ ((p.expectedTotalFee - ifnull(p.subContractFee,0)) * 0.8) >= :lowerLimit "
+ " and sum(t.consumedBudget)/ ((p.expectedTotalFee - ifnull(p.subContractFee,0)) * 0.8) <= 1 "
+ " or(sum(t.normalConsumed+COALESCE(t.otConsumed, 0))/ COALESCE(p.totalManhour, 0)) >= :lowerLimit "
+ " and (sum(t.normalConsumed+COALESCE(t.otConsumed, 0))/ COALESCE(p.totalManhour, 0)) <= 1 "
+ " then'Potential Overconsumption' else'Within Budget' END as status "
+ " from "
+ " ( "
+ " SELECT t.*, (t.normalConsumed+COALESCE(t.otConsumed, 0)) * sal.hourlyRate as consumedBudget "
+ " FROM timesheet t "
+ " INNER JOIN salary_effective se ON se.staffId = t.staffId AND t.recordDate BETWEEN se.startDate AND se.endDate "
+ " LEFT JOIN salary sal on se.salaryId = sal.salaryPoint "
+ " ) t "
+ " left join project p on p.id = t.projectId "
+ " left join team tm on p.teamLead = tm.teamLead "
+ " left join customer c on c.id = p.customerId "
+ " left join subsidiary ss on p.customerSubsidiaryId = ss.id "
+ " WHERE p.deleted = false and p.status ='On-going' "
)
var statusFilter: String = ""
if (args != null) {
@@ -3699,21 +3685,38 @@ open class ReportService(
rowIndex = 3
sortedTeams.forEach { team: Team ->
// not his/her team staffs
val staffs = timesheets
var staffs = timesheets
.filter { it.project?.teamLead?.team?.id == team.id && it.staff?.team?.id != team.id }
.mapNotNull { it.staff }
.sortedBy { it.staffId }
.distinct()



// his/her team projects
val projects = timesheets
.filter { it.project?.teamLead?.team?.id == team.id && it.project?.teamLead?.team?.id != it.staff?.team?.id }
var tempTimesheets = timesheets
.filter {
it.project?.teamLead?.team?.id == team.id
&& it.project?.teamLead?.team?.id != it.staff?.team?.id
}

if (teamId.lowercase() != "all" && teamId.toLong() != team.id) {
staffs = staffs.filter {
it.team.id == teamId.toLong()
}

tempTimesheets = tempTimesheets.filter {
it.staff?.team?.id == teamId.toLong()
}
}

val projects = tempTimesheets
.mapNotNull { it.project }
.sortedByDescending { it.code }
.distinct()

// Team
if (projects.isNotEmpty()) {
if (projects.isNotEmpty() && staffs.isNotEmpty()) {
sheet.createRow(rowIndex++).apply {
createCell(0).apply {
setCellValue("Team to be charged:")
@@ -3787,7 +3790,7 @@ open class ReportService(
val startRow = rowIndex + 1
var endRow = rowIndex
projects.forEach { project: Project ->
if (teamId.lowercase() == "all" || teamId.toLong() == project.teamLead?.team?.id || teamId.toLong() == team.id) {
if (teamId.lowercase() == "all" || teamId.toLong() == project.teamLead?.team?.id || teamId.toLong() == team.id || team.id == project.teamLead?.team?.id) {
// if (team.id == project.teamLead?.team?.id) {
endRow++
sheet.createRow(rowIndex++).apply {
@@ -3836,7 +3839,7 @@ open class ReportService(
}

createCell(columnIndex).apply {
setCellValue(totalSalary)
setCellValue(totalSalary * chargeFee)
val cloneStyle = workbook.createCellStyle()
cloneStyle.cloneStyleFrom(boldFontWithBorderStyle)
cellStyle = cloneStyle.apply {


+ 0
- 1
src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt Dosyayı Görüntüle

@@ -172,7 +172,6 @@ class ReportController(
val args: MutableMap<String, Any> = mutableMapOf(
"status" to request.status,
"lowerLimit" to lowerLimit

)
if (request.teamId != null) {
args["teamId"] = request.teamId


+ 1
- 1
src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt Dosyayı Görüntüle

@@ -288,7 +288,7 @@ open class TimesheetsService(
logger.info("---------project task-------")
val projectTask = null

val nonBillableTask = taskRepository.findById(44).orElseThrow()
val nonBillableTask = taskRepository.findById(42).orElseThrow()
// process record date
logger.info("---------record date-------")
val formatter = DateTimeFormatter.ofPattern("dd-MMM-yyyy")


+ 24
- 0
src/main/resources/db/changelog/changes/20240814_01_cyril/01_update_timesheet_and_task.sql Dosyayı Görüntüle

@@ -0,0 +1,24 @@
-- liquibase formatted sql
-- changeset cyril:timesheet,project_task,task_template_tasks,task

UPDATE timesheet t, project_task pt1, project_task pt2
SET t.projectTaskId = pt2.id
WHERE t.projectTaskId = pt1.id and t.projectId = pt2.project_id and pt2.task_id = 44 and (pt1.task_id = 42 or pt1.task_id = 43);

UPDATE timesheet SET nonBillableTaskId = 42 where nonBillableTaskId in (43, 44);

DELETE FROM project_task WHERE task_id in (42, 43);

UPDATE project_task SET task_id = 42 WHERE task_id = 44;

DELETE FROM task_template_tasks WHERE tasksId in (42, 43);

UPDATE task_template_tasks SET tasksId = 42 WHERE tasksId = 44;

DELETE FROM task
WHERE id=43;
DELETE FROM task
WHERE id=44;
UPDATE task
SET name='5.9 Others and remain fast time input'
WHERE id=42;

+ 5
- 0
src/main/resources/db/changelog/changes/20240815_01_cyril/01_update_project.sql Dosyayı Görüntüle

@@ -0,0 +1,5 @@
-- liquibase formatted sql
-- changeset cyril:project

ALTER TABLE `project`
ADD COLUMN `ratePerManhour` DOUBLE NULL DEFAULT NULL AFTER `totalManhour`;

Yükleniyor…
İptal
Kaydet