소스 검색

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

tags/Baseline_30082024_BACKEND_UAT
MSI\2Fi 1 년 전
부모
커밋
7982282383
39개의 변경된 파일1645개의 추가작업 그리고 317개의 파일을 삭제
  1. +1
    -0
      src/main/java/com/ffii/tsms/modules/data/entity/BuildingTypeRepository.kt
  2. +4
    -0
      src/main/java/com/ffii/tsms/modules/data/entity/CustomerRepository.java
  3. +2
    -0
      src/main/java/com/ffii/tsms/modules/data/entity/LocationRepository.kt
  4. +1
    -0
      src/main/java/com/ffii/tsms/modules/data/entity/StaffRepository.java
  5. +1
    -0
      src/main/java/com/ffii/tsms/modules/data/entity/Subsidiary.java
  6. +6
    -0
      src/main/java/com/ffii/tsms/modules/data/entity/SubsidiaryRepository.java
  7. +2
    -0
      src/main/java/com/ffii/tsms/modules/data/entity/TeamRepository.java
  8. +1
    -0
      src/main/java/com/ffii/tsms/modules/data/entity/WorkNatureRepository.kt
  9. +12
    -0
      src/main/java/com/ffii/tsms/modules/data/service/CustomerService.kt
  10. +415
    -90
      src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt
  11. +11
    -0
      src/main/java/com/ffii/tsms/modules/data/service/SubsidiaryService.kt
  12. +24
    -0
      src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt
  13. +1
    -1
      src/main/java/com/ffii/tsms/modules/data/web/SkillController.kt
  14. +2
    -0
      src/main/java/com/ffii/tsms/modules/project/entity/InvoiceRepository.kt
  15. +3
    -0
      src/main/java/com/ffii/tsms/modules/project/entity/Project.kt
  16. +2
    -0
      src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt
  17. +4
    -4
      src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt
  18. +344
    -25
      src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt
  19. +24
    -2
      src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt
  20. +2
    -1
      src/main/java/com/ffii/tsms/modules/project/web/models/EditProjectDetails.kt
  21. +1
    -0
      src/main/java/com/ffii/tsms/modules/project/web/models/MainProjectDetails.kt
  22. +6
    -3
      src/main/java/com/ffii/tsms/modules/project/web/models/NewProjectRequest.kt
  23. +476
    -158
      src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
  24. +40
    -2
      src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt
  25. +4
    -0
      src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt
  26. +2
    -0
      src/main/java/com/ffii/tsms/modules/timesheet/entity/TimesheetRepository.kt
  27. +125
    -24
      src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt
  28. +22
    -0
      src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt
  29. +7
    -7
      src/main/java/com/ffii/tsms/modules/user/service/UserService.java
  30. +9
    -0
      src/main/resources/db/changelog/changes/20240617_01_cyril/01_update_task.sql
  31. +5
    -0
      src/main/resources/db/changelog/changes/20240617_01_cyril/02_update_project.sql
  32. +81
    -0
      src/main/resources/db/changelog/changes/20240702_01_cyril/01_update_authority.sql
  33. +5
    -0
      src/main/resources/db/changelog/changes/20240704_01_cyril/01_update_project.sql
  34. BIN
      src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx
  35. BIN
      src/main/resources/templates/report/AR04_Cost and Expense Report v02.xlsx
  36. BIN
      src/main/resources/templates/report/AR05_Project Completion Report.xlsx
  37. BIN
      src/main/resources/templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx
  38. BIN
      src/main/resources/templates/report/AR08_Monthly Work Hours Analysis Report.xlsx
  39. BIN
      src/main/resources/templates/report/Cross Team Charge Report.xlsx

+ 1
- 0
src/main/java/com/ffii/tsms/modules/data/entity/BuildingTypeRepository.kt 파일 보기

@@ -3,4 +3,5 @@ package com.ffii.tsms.modules.data.entity;
import com.ffii.core.support.AbstractRepository

interface BuildingTypeRepository : AbstractRepository<BuildingType, Long> {
fun findByName(name: String): BuildingType?
}

+ 4
- 0
src/main/java/com/ffii/tsms/modules/data/entity/CustomerRepository.java 파일 보기

@@ -3,6 +3,7 @@ package com.ffii.tsms.modules.data.entity;
import java.util.Optional;
import java.util.List;

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import com.ffii.core.support.AbstractRepository;
@@ -11,4 +12,7 @@ public interface CustomerRepository extends AbstractRepository<Customer, Long> {
List<Customer> findAllByDeletedFalse();
Optional<Customer> findByCode(@Param("code") String code);
Optional<Customer> findByName(@Param("name") String name);

@Query("SELECT max(cast(substring_index(c.code, '-', -1) as long)) FROM Customer c")
Long getLatestCodeNumber();
}

+ 2
- 0
src/main/java/com/ffii/tsms/modules/data/entity/LocationRepository.kt 파일 보기

@@ -3,4 +3,6 @@ package com.ffii.tsms.modules.data.entity;
import com.ffii.core.support.AbstractRepository

interface LocationRepository : AbstractRepository<Location, Long> {

fun findByName(name: String): Location
}

+ 1
- 0
src/main/java/com/ffii/tsms/modules/data/entity/StaffRepository.java 파일 보기

@@ -25,4 +25,5 @@ public interface StaffRepository extends AbstractRepository<Staff, Long> {
Optional<List<Staff>> findAllByDeletedFalse();

Optional<Staff> findIdAndNameByUserIdAndDeletedFalse(Long id);

}

+ 1
- 0
src/main/java/com/ffii/tsms/modules/data/entity/Subsidiary.java 파일 보기

@@ -10,6 +10,7 @@ import java.util.List;

import static jakarta.persistence.CascadeType.ALL;


@Entity
@Table(name = "subsidiary")
public class Subsidiary extends BaseEntity<Long> {


+ 6
- 0
src/main/java/com/ffii/tsms/modules/data/entity/SubsidiaryRepository.java 파일 보기

@@ -1,6 +1,7 @@
package com.ffii.tsms.modules.data.entity;

import com.ffii.core.support.AbstractRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;
@@ -12,4 +13,9 @@ public interface SubsidiaryRepository extends AbstractRepository<Subsidiary, Lon
List<Subsidiary> findAllByDeletedFalseAndIdIn(List<Long> id);

Optional<Subsidiary> findByCode(@Param("code") String code);

Optional<Subsidiary> findByName(@Param("name") String name);

@Query("SELECT max(cast(substring_index(s.code, '-', -1) as long)) FROM Subsidiary s")
Long getLatestCodeNumber();
}

+ 2
- 0
src/main/java/com/ffii/tsms/modules/data/entity/TeamRepository.java 파일 보기

@@ -9,4 +9,6 @@ public interface TeamRepository extends AbstractRepository<Team, Long> {
List<Team> findByDeletedFalse();

Team findByStaff(Staff staff);

Team findByCode(String code);
}

+ 1
- 0
src/main/java/com/ffii/tsms/modules/data/entity/WorkNatureRepository.kt 파일 보기

@@ -3,4 +3,5 @@ package com.ffii.tsms.modules.data.entity;
import com.ffii.core.support.AbstractRepository

interface WorkNatureRepository : AbstractRepository<WorkNature, Long> {
fun findByName(name: String): WorkNature?
}

+ 12
- 0
src/main/java/com/ffii/tsms/modules/data/service/CustomerService.kt 파일 보기

@@ -37,6 +37,18 @@ open class CustomerService(
return customerRepository.findByCode(code);
}

open fun createClientCode(): String {
val prefix = "CT"

val latestClientCode = customerRepository.getLatestCodeNumber()

if (latestClientCode != null) {
return "$prefix-" + String.format("%03d", latestClientCode + 1L)
} else {
return "$prefix-001"
}
}

open fun saveCustomer(saveCustomer: SaveCustomerRequest): SaveCustomerResponse {

val duplicateCustomer = findCustomerByCode(saveCustomer.code)


+ 415
- 90
src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt 파일 보기

@@ -68,19 +68,23 @@ open class DashboardService(

fun searchCustomerSubsidiaryProject(args: Map<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder("select"
+ " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id,"
+ " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id,"
+ " p.id as id,"
+ " p.id as projectId,"
+ " p.code as projectCode,"
+ " p.name as projectName,"
+ " te.code as team,"
+ " s.name as teamLead,"
+ " tg.name as expectedStage,"
+ " GROUP_CONCAT(DISTINCT tg.name ORDER BY tg.name) as expectedStage,"
+ " p.totalManhour as budgetedManhour,"
+ " sum(t.normalConsumed) + sum(t.otConsumed) as spentManhour,"
+ " p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed) as remainedManhour,"
+ " coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) as manhourConsumptionPercentage,"
+ " COALESCE (DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d'),'NA') as comingPaymentMilestone"
+ " COALESCE (DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d'),'NA') as comingPaymentMilestone,"
+ " case"
+ " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) > 0 then 0"
+ " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) <= 0 then 1"
+ " end as alert"
+ " from project p"
+ " left join project_task pt on p.id = pt.project_id"
+ " left join timesheet t on pt.id = t.projectTaskId"
@@ -105,7 +109,7 @@ open class DashboardService(
+ " ) milestonePayment on milestonePayment.pid = p.id"
+ " where p.customerId = :customerId"
+ " and p.customerSubsidiaryId = :subsidiaryId"
+ " and (tg.name != '5. Miscellaneous' or tg.name is null)"
// + " and (tg.name != '5. Miscellaneous' or tg.name is null)"
+ " and p.status not in (\"Pending to Start\",\"Completed\",\"Deleted\")"
)

@@ -116,25 +120,38 @@ open class DashboardService(
}
}

sql.append(" group by p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone")
sql.append(" group by p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone")

if (args["tableSorting"] == "ProjectName") {
sql.append(" ORDER BY p.name ASC")
} else if (args["tableSorting"] == "PercentageASC") {
sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) asc")
} else if (args["tableSorting"] == "PercentageDESC") {
sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) desc")
}

return jdbcDao.queryForList(sql.toString(), args)
}

fun searchCustomerNonSubsidiaryProject(args: Map<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder("select"
+ " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id,"
+ " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id,"
+ " p.id as id,"
+ " p.id as projectId,"
+ " p.code as projectCode,"
+ " p.name as projectName,"
+ " te.code as team,"
+ " s.name as teamLead,"
+ " tg.name as expectedStage,"
+ " GROUP_CONCAT(DISTINCT tg.name ORDER BY tg.name) as expectedStage,"
+ " p.totalManhour as budgetedManhour,"
+ " COALESCE (sum(t.normalConsumed) + sum(t.otConsumed),0) as spentManhour,"
+ " COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) as remainedManhour,"
+ " coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) as manhourConsumptionPercentage,"
+ " COALESCE (DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d'),'NA') as comingPaymentMilestone"
+ " COALESCE (DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d'),'NA') as comingPaymentMilestone,"
+ " case"
+ " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) > 0 then 0"
+ " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) <= 0 then 1"
+ " end as alert"
+ " from project p"
+ " left join project_task pt on p.id = pt.project_id"
+ " left join timesheet t on pt.id = t.projectTaskId"
@@ -159,10 +176,16 @@ open class DashboardService(
+ " where p.customerId = :customerId"
+ " and isNull(p.customerSubsidiaryId)"
+ " and p.status not in (\"Pending to Start\",\"Completed\",\"Deleted\")"
+ " and (tg.name != '5. Miscellaneous' or tg.name is null)"
+ " group by p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone"
// + " and (tg.name != '5. Miscellaneous' or tg.name is null)"
+ " group by p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone"
)

if (args["tableSorting"] == "ProjectName") {
sql.append(" ORDER BY p.name ASC")
} else if (args["tableSorting"] == "PercentageASC") {
sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) asc")
} else if (args["tableSorting"] == "PercentageDESC") {
sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) desc")
}
return jdbcDao.queryForList(sql.toString(), args)
}

@@ -276,19 +299,23 @@ open class DashboardService(
fun searchTeamProject(args: Map<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder(
"select"
+ " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id,"
+ " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id,"
+ " p.id as id,"
+ " p.id as projectId,"
+ " p.code as projectCode,"
+ " p.name as projectName,"
+ " te.code as team,"
+ " s.name as teamLead,"
+ " tg.name as expectedStage,"
+ " GROUP_CONCAT(DISTINCT tg.name ORDER BY tg.name) as expectedStage,"
+ " p.totalManhour as budgetedManhour,"
+ " COALESCE (sum(t.normalConsumed) + sum(t.otConsumed),0) as spentManhour,"
+ " COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) as remainedManhour,"
+ " coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) as manhourConsumptionPercentage,"
+ " COALESCE (DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d'),'NA') as comingPaymentMilestone"
+ " COALESCE (DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d'),'NA') as comingPaymentMilestone,"
+ " case"
+ " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) > 0 then 0"
+ " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) <= 0 then 1"
+ " end as alert"
+ " from project p"
+ " left join project_task pt on p.id = pt.project_id"
+ " left join timesheet t on pt.id = t.projectTaskId"
@@ -312,12 +339,111 @@ open class DashboardService(
+ " ) milestonePayment on milestonePayment.pid = p.id"
+ " where p.teamLead = :teamLeadId"
+ " and p.status not in (\"Pending to Start\",\"Completed\",\"Deleted\")"
+ " and (tg.name != '5. Miscellaneous' or tg.name is null)"
+ " group by p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone"
// + " and (tg.name != '5. Miscellaneous' or tg.name is null)"
+ " group by p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone"
)
if (args["tableSorting"] == "ProjectName") {
sql.append(" ORDER BY p.name ASC")
} else if (args["tableSorting"] == "PercentageASC") {
sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) asc")
} else if (args["tableSorting"] == "PercentageDESC") {
sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) desc")
}

return jdbcDao.queryForList(sql.toString(), args)
}

fun searchTeamConsumption(args: Map<String, Any>): List<Map<String, Any>>
{
val sql = StringBuilder(
// "select"
// + " ROW_NUMBER() OVER (ORDER BY te.code, s.name, project.budgetedManhour) AS id,"
// + " te.code as team,"
// + " s.name as teamLead,"
// + " project.budgetedManhour as budgetedManhour,"
// + " COALESCE (sum(t.normalConsumed) + sum(t.otConsumed),0) as spentManhour,"
// + " COALESCE (project.budgetedManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) as remainedManhour,"
// + " coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/project.budgetedManhour)*100,2),0) as manhourConsumptionPercentage,"
// + " case"
// + " when COALESCE (project.budgetedManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) > 0 then 0"
// + " when COALESCE (project.budgetedManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) <= 0 then 1"
// + " end as alert"
// + " from team te"
// + " left join project p on p.teamLead = te.teamLead"
// + " left join project_task pt on p.id = pt.project_id"
// + " left join timesheet t on pt.id = t.projectTaskId"
// + " left join staff s on te.teamLead = s.id"
// + " left join ("
// + " select sum(p2.totalManhour) as budgetedManhour"
// + " from team t2"
// + " left join project p2 on p2.teamLead = t2.teamLead"
// + " where p2.teamLead in (:teamIds)"
// + " ) as project on 1 = 1"
// + " where p.teamLead in (:teamIds)"
// + " and p.status not in ('Pending to Start','Completed','Deleted')"
// + " group by te.code, s.name, project.budgetedManhour"
"select"
+ " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id,"
+ " p.id as id,"
+ " p.id as projectId,"
+ " p.code as projectCode,"
+ " p.name as projectName,"
+ " te.code as team,"
+ " s.name as teamLead,"
+ " GROUP_CONCAT(DISTINCT tg.name ORDER BY tg.name) as expectedStage,"
+ " p.totalManhour as budgetedManhour,"
+ " COALESCE (sum(t.normalConsumed) + sum(t.otConsumed),0) as spentManhour,"
+ " COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) as remainedManhour,"
+ " coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) as manhourConsumptionPercentage,"
+ " COALESCE (DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d'),'NA') as comingPaymentMilestone,"
+ " case"
+ " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) > 0 then 0"
+ " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) <= 0 then 1"
+ " end as alert"
+ " from project p"
+ " left join project_task pt on p.id = pt.project_id"
+ " left join timesheet t on pt.id = t.projectTaskId"
+ " left join team te on p.teamLead = te.teamLead"
+ " left join staff s on te.teamLead = s.id"
+ " left join milestone m on p.id = m.projectId and curdate() >= m.startDate and curdate() <= m.endDate"
+ " left join task_group tg on m.taskGroupId = tg.id"
+ " left join ("
+ " SELECT pid, MIN(comingPaymentMilestone) AS comingPaymentMilestone"
+ " FROM ("
+ " SELECT p.id AS pid, mp.date AS comingPaymentMilestone"
+ " FROM project p"
+ " LEFT JOIN milestone m ON p.id = m.projectId"
+ " LEFT JOIN milestone_payment mp ON m.id = mp.milestoneId"
+ " WHERE p.teamLead in (:teamIds)"
+ " AND p.status NOT IN ('Pending to Start', 'Completed', 'Deleted')"
+ " AND mp.date >= CURDATE()"
+ " ) AS subquery"
+ " GROUP BY pid"
+ " ORDER BY comingPaymentMilestone ASC"
+ " ) milestonePayment on milestonePayment.pid = p.id"
+ " where p.teamLead in (:teamIds)"
+ " and p.status not in ('Pending to Start','Completed','Deleted')"
+ " group by p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone"
)

// if (args["tableSorting"] == "ProjectName") {
// sql.append(" ORDER BY te.code ASC")
// } else if (args["tableSorting"] == "PercentageASC") {
// sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/project.budgetedManhour)*100,2),0) asc")
// } else if (args["tableSorting"] == "PercentageDESC") {
// sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/project.budgetedManhour)*100,2),0) desc")
// }
if (args["tableSorting"] == "ProjectName") {
sql.append(" ORDER BY p.name ASC")
} else if (args["tableSorting"] == "PercentageASC") {
sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) asc")
} else if (args["tableSorting"] == "PercentageDESC") {
sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) desc")
}

return jdbcDao.queryForList(sql.toString(), args)
}

fun searchFinancialSummaryCard(args: Map<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder(
"select"
@@ -327,13 +453,19 @@ open class DashboardService(
+ " coalesce(pj.totalFee,0) as totalFee,"
+ " coalesce(pj.totalBudget,0) as totalBudget,"
+ " coalesce(sum(i.issueAmount),0) as totalInvoiced,"
+ " coalesce(pj.totalFee,0) - coalesce(sum(i.issueAmount),0) as unInvoiced,"
+ " coalesce(sum(i.paidAmount),0) as totalReceived,"
+ " round(expenditure.cumulativeExpenditure,2) as cumulativeExpenditure,"
+ " case"
+ " when coalesce(round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2),0) >= 1 then 'Positive'"
+ " when coalesce(round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2),0) < 1 then 'Negative'"
+ " end as cashFlowStatus,"
+ " coalesce(round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2),0) as cpi"
+ " coalesce(round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2),0) as cpi,"
+ " coalesce(round(coalesce(pj.totalFee,0) / (expenditure.cumulativeExpenditure),2),0) as projectedCpi,"
+ " case"
+ " when coalesce(round(coalesce(pj.totalFee,0) / (expenditure.cumulativeExpenditure),2),0) >= 1 then 'Positive'"
+ " when coalesce(round(coalesce(pj.totalFee,0) / (expenditure.cumulativeExpenditure),2),0) < 1 then 'Negative'"
+ " end as projectedCashFlowStatus"
+ " from team t"
+ " left join ("
+ " select"
@@ -404,12 +536,18 @@ open class DashboardService(
+ " round(sum(p.expectedTotalFee) * 0.8,2) as totalBudget,"
+ " round(expenditure.cumulativeExpenditure,2) as cumulativeExpenditure,"
+ " sum(i.issueAmount) as totalInvoiced,"
+ " sum(p.expectedTotalFee) - sum(i.issueAmount) as unInvoiced,"
+ " sum(i.paidAmount) as totalReceived,"
+ " case"
+ " when round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2) >= 1 then 'Positive'"
+ " when round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2) < 1 then 'Negative'"
+ " end as cashFlowStatus,"
+ " round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2) as cpi"
+ " round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2) as cpi,"
+ " round(sum(p.expectedTotalFee) / (expenditure.cumulativeExpenditure),2) as projectedCpi,"
+ " case"
+ " when round(sum(p.expectedTotalFee) / (expenditure.cumulativeExpenditure),2) >= 1 then 'Positive'"
+ " when round(sum(p.expectedTotalFee) / (expenditure.cumulativeExpenditure),2) < 1 then 'Negative'"
+ " end as projectedCashFlowStatus"
+ " from project p"
+ " left join ("
+ " select"
@@ -445,16 +583,103 @@ open class DashboardService(
return jdbcDao.queryForList(sql.toString(), args)
}

// " case"
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) <= COALESCE(expenditure.cumulativeExpenditure,0) then coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) - coalesce(sum(i.issueAmount),0)"
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) < coalesce(sum(i.issueAmount),0) then 0"
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) >= coalesce(sum(i.issueAmount),0) then COALESCE(expenditure.cumulativeExpenditure,0) - coalesce(sum(i.issueAmount),0)"
// + " end as totalUninvoiced"

fun searchFinancialSummaryByClient(args: Map<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder( "select"
+ " ROW_NUMBER() OVER (ORDER BY t.id, c.id) AS id,"
// val sql = StringBuilder( "select"
// + " ROW_NUMBER() OVER (ORDER BY t.id, c.id) AS id,"
// + " t.id as teamId,"
// + " c.id as cid,"
// + " c.code as customerCode,"
// + " c.name as customerName,"
// + " count(p.name) as projectNo,"
// + " sum(p.expectedTotalFee) as totalFee,"
// + " round(sum(p.expectedTotalFee) * 0.8,2) as totalBudget,"
// + " COALESCE(round(expenditure.cumulativeExpenditure,2),0) as cumulativeExpenditure,"
// + " coalesce(sum(i.issueAmount),0) as totalInvoiced,"
// + " coalesce(sum(i.paidAmount),0) as totalReceived,"
// + " case"
// + " when coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) >= 1 then 'Positive'"
// + " when coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) < 1 then 'Negative'"
// + " end as cashFlowStatus,"
// + " coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) as cpi,"
// + " case"
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) <= COALESCE(expenditure.cumulativeExpenditure,0) then coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) - coalesce(sum(i.issueAmount),0)"
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) < coalesce(sum(i.issueAmount),0) then 0"
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) >= coalesce(sum(i.issueAmount),0) then COALESCE(expenditure.cumulativeExpenditure,0) - coalesce(sum(i.issueAmount),0)"
// + " end as totalUninvoiced"
// + " from team t"
// + " left join project p on t.teamLead = p.teamLead"
// + " left join customer c on p.customerId = c.id"
// + " left join ("
// + " select"
// + " t3.id as tid,"
// + " c3.id as cid,"
// + " sum(i3.issueAmount) as issueAmount,"
// + " sum(i3.paidAmount) as paidAmount"
// + " from team t3"
// + " left join project p3 on t3.teamLead = p3.teamLead"
// + " left join customer c3 on p3.customerId = c3.id"
// + " left join invoice i3 on p3.code = i3.projectCode"
// + " where t3.deleted = 0"
// + " and p3.status = 'On-going'"
//
// )
//
// if (args != null) {
// if (args.containsKey("teamId"))
// sql.append(" AND t3.id = :teamId");
// }
// sql.append( " group by c3.id, t3.id"
// + " ) as i on i.cid = c.id and i.tid = t.id"
// + " left join ("
// + " select"
// + " r.teamId as teamId,"
// + " r.customerId as customerId,"
// + " sum(r.cumulativeExpenditure) as cumulativeExpenditure"
// + " from ("
// + " select"
// + " t1.id as teamId,"
// + " c2.id as customerId,"
// + " (coalesce(sum(t2.normalConsumed),0) * s2.hourlyRate) + (coalesce(sum(t2.otConsumed),0) * s2.hourlyRate * 1.0) as cumulativeExpenditure,"
// + " s2.hourlyRate as hourlyRate,"
// + " sum(t2.normalConsumed) as normalConsumed,"
// + " sum(t2.otConsumed) as otConsumed"
// + " from team t1"
// + " left join project p2 on t1.teamLead = p2.teamLead"
// + " left join customer c2 on p2.customerId = c2 .id"
// + " left join project_task pt ON p2.id = pt.project_id"
// + " left join timesheet t2 on pt.id = t2.projectTaskId"
// + " left join staff s on t2.staffId = s.id"
// + " left join salary s2 on s.salaryId = s2.salaryPoint"
// + " where p2.status = 'On-going'"
// + " and t2.id is not null"
// + " group by s2.hourlyRate,t1.id,c2.id"
// + " ) as r"
// + " group by r.teamId, r.customerId"
// + " ) as expenditure on expenditure.teamId = t.id and expenditure.customerId = c.id"
// + " where t.deleted = 0"
// + " and p.status = 'On-going'")
//
// if (args != null) {
// if (args.containsKey("teamId"))
// sql.append(" AND t.id = :teamId");
// }
// sql.append(" group by t.id, c.id, c.code, c.name")
val sql = StringBuilder(
"select"
+ " ROW_NUMBER() OVER (ORDER BY t.id, i.cid) AS id,"
+ " t.id as teamId,"
+ " c.id as cid,"
+ " c.code as customerCode,"
+ " c.name as customerName,"
+ " count(p.name) as projectNo,"
+ " sum(p.expectedTotalFee) as totalFee,"
+ " round(sum(p.expectedTotalFee) * 0.8,2) as totalBudget,"
+ " i.cid as cid,"
+ " i.customerCode as customerCode,"
+ " i.customerName as customerName,"
+ " p.projectNo as projectNo,"
+ " p.totalFee as totalFee,"
+ " p.totalBudget as totalBudget,"
+ " COALESCE(round(expenditure.cumulativeExpenditure,2),0) as cumulativeExpenditure,"
+ " coalesce(sum(i.issueAmount),0) as totalInvoiced,"
+ " coalesce(sum(i.paidAmount),0) as totalReceived,"
@@ -463,18 +688,23 @@ open class DashboardService(
+ " when coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) < 1 then 'Negative'"
+ " end as cashFlowStatus,"
+ " coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) as cpi,"
+ " coalesce(round(p.totalFee / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) as projectedCpi,"
+ " case"
+ " when coalesce(round(p.totalFee / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) >= 1 then 'Positive'"
+ " when coalesce(round(p.totalFee / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) < 1 then 'Negative'"
+ " end as projectedCashFlowStatus,"
+ " case"
+ " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) <= COALESCE(expenditure.cumulativeExpenditure,0) then coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) - coalesce(sum(i.issueAmount),0)"
+ " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) < coalesce(sum(i.issueAmount),0) then 0"
+ " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) >= coalesce(sum(i.issueAmount),0) then COALESCE(expenditure.cumulativeExpenditure,0) - coalesce(sum(i.issueAmount),0)"
+ " when p.totalFee - sum(i.issueAmount) >= 0 then coalesce(round(p.totalFee - sum(i.issueAmount),2),0)"
+ " when p.totalFee - sum(i.issueAmount) < 0 then 0"
+ " when p.totalFee - sum(i.issueAmount) is null then 0"
+ " end as totalUninvoiced"
+ " from team t"
+ " left join project p on t.teamLead = p.teamLead"
+ " left join customer c on p.customerId = c.id"
+ " left join ("
+ " select"
+ " t3.id as tid,"
+ " c3.id as cid,"
+ " c3.code as customerCode,"
+ " c3.name as customerName,"
+ " sum(i3.issueAmount) as issueAmount,"
+ " sum(i3.paidAmount) as paidAmount"
+ " from team t3"
@@ -483,15 +713,34 @@ open class DashboardService(
+ " left join invoice i3 on p3.code = i3.projectCode"
+ " where t3.deleted = 0"
+ " and p3.status = 'On-going'"

)

if (args != null) {
if (args.containsKey("teamId"))
)
if (args != null) {
if (args.containsKey("teamId"))
sql.append(" AND t3.id = :teamId");
}
sql.append( " group by c3.id, t3.id"
+ " ) as i on i.cid = c.id and i.tid = t.id"
}
sql.append(
" group by c3.id, t3.id, customerCode, customerName"
+ " ) as i on i.tid = t.id"
+ " left join ("
+ " select"
+ " t.id as tid,"
+ " c.id as cid,"
+ " count(p.id) as projectNo,"
+ " sum(p.expectedTotalFee) as totalFee,"
+ " round(sum(p.expectedTotalFee) * 0.8,2) as totalBudget"
+ " from team t"
+ " left join project p on t.teamLead = p.teamLead"
+ " left join customer c on p.customerId = c.id"
+ " where t.deleted = 0"
+ " and p.status = 'On-going'"
)
if (args != null) {
if (args.containsKey("teamId"))
sql.append(" AND t.id = :teamId");
}
sql.append(
" group by t.id, c.id"
+ " ) as p on p.tid = t.id and p.cid = i.cid"
+ " left join ("
+ " select"
+ " r.teamId as teamId,"
@@ -517,15 +766,15 @@ open class DashboardService(
+ " group by s2.hourlyRate,t1.id,c2.id"
+ " ) as r"
+ " group by r.teamId, r.customerId"
+ " ) as expenditure on expenditure.teamId = t.id and expenditure.customerId = c.id"
+ " ) as expenditure on expenditure.teamId = t.id and expenditure.customerId = i.cid"
+ " where t.deleted = 0"
+ " and p.status = 'On-going'")
if (args != null) {
if (args.containsKey("teamId"))
+ " and i.cid is not null"
)
if (args != null) {
if (args.containsKey("teamId"))
sql.append(" AND t.id = :teamId");
}
sql.append(" group by t.id, c.id, c.code, c.name")
}
sql.append( " group by t.id, i.cid, i.customerCode, i.customerName, p.projectNo, p.totalFee, p.totalBudget")

return jdbcDao.queryForList(sql.toString(), args)
}
@@ -547,10 +796,20 @@ open class DashboardService(
+ " when coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) < 1 then 'Negative'"
+ " end as cashFlowStatus,"
+ " coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) as cpi,"
+ " coalesce(round(p.expectedTotalFee / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) as projectedCpi,"
+ " case"
+ " when coalesce(round(p.expectedTotalFee / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) >= 1 then 'Positive'"
+ " when coalesce(round(p.expectedTotalFee / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) < 1 then 'Negative'"
+ " end as projectedCashFlowStatus,"
// + " case"
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) <= COALESCE(expenditure.cumulativeExpenditure,0) then coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) - coalesce(sum(i.issueAmount),0)"
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) < coalesce(sum(i.issueAmount),0) then 0"
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) >= coalesce(sum(i.issueAmount),0) then COALESCE(expenditure.cumulativeExpenditure,0) - coalesce(sum(i.issueAmount),0)"
// + " end as totalUninvoiced"
+ " case"
+ " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) <= COALESCE(expenditure.cumulativeExpenditure,0) then coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) - coalesce(sum(i.issueAmount),0)"
+ " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) < coalesce(sum(i.issueAmount),0) then 0"
+ " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) >= coalesce(sum(i.issueAmount),0) then COALESCE(expenditure.cumulativeExpenditure,0) - coalesce(sum(i.issueAmount),0)"
+ " when p.expectedTotalFee - sum(i.issueAmount) >= 0 then coalesce(round(p.expectedTotalFee - sum(i.issueAmount),2),0)"
+ " when p.expectedTotalFee - sum(i.issueAmount) < 0 then 0"
+ " when p.expectedTotalFee - sum(i.issueAmount) is null then 0"
+ " end as totalUninvoiced"
+ " from team t"
+ " left join project p on t.teamLead = p.teamLead"
@@ -739,7 +998,9 @@ open class DashboardService(
+ " coalesce (sum(i.issueAmount) - sum(i.paidAmount),0) as receivable,"
+ " coalesce (round(sum(p.expectedTotalFee)*0.8,2),0) as totalBudget,"
+ " coalesce (expenditure.expenditure,0) as totalExpenditure,"
+ " coalesce (sum(p.expectedTotalFee)*0.8 - expenditure.expenditure,0) as expenditureReceivable"
+ " coalesce (sum(p.expectedTotalFee)*0.8 - expenditure.expenditure,0) as expenditureReceivable,"
+ " sum(p.expectedTotalFee) as totalProjectFee,"
+ " coalesce (round(sum(i.issueAmount)/sum(p.expectedTotalFee)*100,0),0) as invoicedPercentage"
+ " from project p"
+ " left join ("
+ " select"
@@ -1083,7 +1344,9 @@ open class DashboardService(
"select"
+ " concat(p.code,'-',p.name) as projectCodeAndName,"
+ " p.expectedTotalFee as totalFee,"
+ " expenditure.expenditure as expenditure,"
+ " p.expectedTotalFee * 0.8 as totalBudget,"
+ " coalesce (expenditure.expenditure,0) as expenditure,"
+ " (p.expectedTotalFee * 0.8) - coalesce (expenditure.expenditure,0) as remainingBudget,"
+ " case"
+ " when p.expectedTotalFee - expenditure.expenditure >= 0 then 'Within Budget'"
+ " when p.expectedTotalFee - expenditure.expenditure < 0 then 'Overconsumption'"
@@ -1189,32 +1452,61 @@ open class DashboardService(
+ " left join staff s on s.id = ts.staffId"
+ " where p.id = :projectId"
+ " group by p.id, t.id, t.name, g.name"
+ " order by p.id;"
+ " order by t.id asc, p.id;"
)

return jdbcDao.queryForList(sql.toString(), args)
}
fun monthlyActualTeamTotalManhoursSpent(args: Map<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder(
" WITH RECURSIVE date_series AS ("
+ " SELECT DATE_FORMAT(:startdate, '%Y-%m-01') AS month"
+ " UNION ALL"
+ " SELECT DATE_FORMAT(DATE_ADD(month, INTERVAL 1 MONTH), '%Y-%m-01')"
+ " FROM date_series"
+ " WHERE month < DATE_FORMAT(:enddate, '%Y-%m-01')"
+ " )"
+ " SELECT"
+ " ds.month AS yearMonth,"
+ " IFNULL(SUM(IFNULL(ts.normalConsumed, 0) + IFNULL(ts.otConsumed, 0)), 0) AS 'TotalManhourConsumed',"
+ " :teamId AS teamId"
+ " FROM date_series ds"
+ " LEFT JOIN staff st"
+ " on st.teamId = :teamId"
+ " LEFT JOIN timesheet ts"
+ " ON DATE_FORMAT(ts.recordDate, '%Y-%m') = DATE_FORMAT(ds.month, '%Y-%m') and ts.staffID = st.id"
+ " WHERE ds.month BETWEEN DATE_FORMAT(:startdate, '%Y-%m-01') AND DATE_FORMAT(:enddate, '%Y-%m-01')"
+ " GROUP BY ds.month, st.teamId"
+ " ORDER BY ds.month"
// " WITH RECURSIVE date_series AS ("
// + " SELECT DATE_FORMAT(:startdate, '%Y-%m-01') AS month"
// + " UNION ALL"
// + " SELECT DATE_FORMAT(DATE_ADD(month, INTERVAL 1 MONTH), '%Y-%m-01')"
// + " FROM date_series"
// + " WHERE month < DATE_FORMAT(:enddate, '%Y-%m-01')"
// + " )"
// + " SELECT"
// + " ds.month AS yearMonth,"
// + " IFNULL(SUM(IFNULL(ts.normalConsumed, 0) + IFNULL(ts.otConsumed, 0)), 0) AS 'TotalManhourConsumed',"
// + " :teamId AS teamId"
// + " FROM date_series ds"
// + " LEFT JOIN staff st"
// + " on st.teamId = :teamId"
// + " LEFT JOIN timesheet ts"
// + " ON DATE_FORMAT(ts.recordDate, '%Y-%m') = DATE_FORMAT(ds.month, '%Y-%m') and ts.staffID = st.id"
// + " WHERE ds.month BETWEEN DATE_FORMAT(:startdate, '%Y-%m-01') AND DATE_FORMAT(:enddate, '%Y-%m-01')"
// + " and ts.recordDate BETWEEN DATE_FORMAT(:startdate, '%Y-%m-01') AND DATE_FORMAT(:enddate, '%Y-%m-01')"
// + " GROUP BY ds.month, st.teamId"
// + " ORDER BY ds.month"
"WITH RECURSIVE date_series AS ("
+ " SELECT DATE_FORMAT(:startdate, '%Y-%m-01') AS month"
+ " UNION ALL"
+ " SELECT DATE_FORMAT(DATE_ADD(month, INTERVAL 1 MONTH), '%Y-%m-01')"
+ " FROM date_series"
+ " WHERE month < DATE_FORMAT(:enddate, '%Y-%m-01')"
+ " )"
+ " SELECT"
+ " ds.month AS yearMonth,"
+ " COALESCE(SUM(COALESCE(ts.normalConsumed, 0) + COALESCE(ts.otConsumed, 0)), 0) AS 'TotalManhourConsumed',"
+ " :teamId AS teamId"
+ " FROM date_series ds"
+ " LEFT JOIN ("
+ " SELECT"
+ " DATE_FORMAT(ts.recordDate, '%Y-%m-01') AS recordMonth,"
+ " ts.staffID,"
+ " SUM(ts.normalConsumed) AS normalConsumed,"
+ " SUM(ts.otConsumed) AS otConsumed"
+ " FROM staff st"
+ " LEFT JOIN timesheet ts"
+ " on ts.staffID = st.id"
+ " WHERE ts.recordDate BETWEEN DATE_FORMAT(:startdate, '%Y-%m-01') AND LAST_DAY(DATE_FORMAT(:enddate, '%Y-%m-%d'))"
+ " and st.teamId = :teamId"
+ " GROUP BY DATE_FORMAT(ts.recordDate, '%Y-%m-01'), staffID"
+ " ) ts ON ds.month = ts.recordMonth"
+ " GROUP BY ds.month"
+ " ORDER BY ds.month;"

)

return jdbcDao.queryForList(sql.toString(), args)
@@ -1253,25 +1545,54 @@ open class DashboardService(
}
fun weeklyActualTeamTotalManhoursSpent(args: Map<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder(
"WITH RECURSIVE date_series AS ("
+ " SELECT DATE_FORMAT(:startdate, '%Y-%m-%d') AS day"
+ " UNION ALL"
+ " SELECT DATE_FORMAT(DATE_ADD(day, INTERVAL 1 DAY), '%Y-%m-%d')"
+ " FROM date_series"
+ " WHERE day < DATE_FORMAT(DATE_ADD(:startdate, INTERVAL 6 DAY), '%Y-%m-%d')"
+ " )"
+ " SELECT"
+ " ds.day AS yearMonth,"
+ " IFNULL(SUM(IFNULL(ts.normalConsumed, 0) + IFNULL(ts.otConsumed, 0)), 0) AS 'TotalManhourConsumed',"
+ " :teamId AS teamId"
+ " FROM date_series ds"
+ " LEFT JOIN staff st"
+ " on st.teamId = :teamId"
+ " LEFT JOIN timesheet ts"
+ " ON DATE_FORMAT(ts.recordDate, '%m-%d') = DATE_FORMAT(ds.day, '%m-%d') and ts.staffID = st.id"
+ " WHERE ds.day BETWEEN DATE_FORMAT(:startdate, '%Y-%m-%d') AND DATE_FORMAT(DATE_ADD(:startdate, INTERVAL 6 DAY), '%Y-%m-%d')"
+ " GROUP BY ds.day, st.teamId"
+ " ORDER BY ds.day"
// "WITH RECURSIVE date_series AS ("
// + " SELECT DATE_FORMAT(:startdate, '%Y-%m-%d') AS day"
// + " UNION ALL"
// + " SELECT DATE_FORMAT(DATE_ADD(day, INTERVAL 1 DAY), '%Y-%m-%d')"
// + " FROM date_series"
// + " WHERE day < DATE_FORMAT(DATE_ADD(:startdate, INTERVAL 6 DAY), '%Y-%m-%d')"
// + " )"
// + " SELECT"
// + " ds.day AS yearMonth,"
// + " IFNULL(SUM(IFNULL(ts.normalConsumed, 0) + IFNULL(ts.otConsumed, 0)), 0) AS 'TotalManhourConsumed',"
// + " :teamId AS teamId"
// + " FROM date_series ds"
// + " LEFT JOIN staff st"
// + " on st.teamId = :teamId"
// + " LEFT JOIN timesheet ts"
// + " ON DATE_FORMAT(ts.recordDate, '%m-%d') = DATE_FORMAT(ds.day, '%m-%d') and ts.staffID = st.id"
// + " WHERE ds.day BETWEEN DATE_FORMAT(:startdate, '%Y-%m-%d') AND DATE_FORMAT(DATE_ADD(:startdate, INTERVAL 6 DAY), '%Y-%m-%d')"
// + " and ts.recordDate BETWEEN DATE_FORMAT(:startdate, '%Y-%m-%d') AND DATE_FORMAT(DATE_ADD(:startdate, INTERVAL 6 DAY), '%Y-%m-%d')"
// + " GROUP BY ds.day, st.teamId"
// + " ORDER BY ds.day"
"WITH RECURSIVE date_series AS ("
+ " SELECT DATE_FORMAT(:startdate, '%Y-%m-%d') AS day"
+ " UNION ALL"
+ " SELECT DATE_FORMAT(DATE_ADD(day, INTERVAL 1 DAY), '%Y-%m-%d')"
+ " FROM date_series"
+ " WHERE day < DATE_FORMAT(DATE_ADD(:startdate, INTERVAL 6 DAY), '%Y-%m-%d')"
+ " )"
+ " SELECT"
+ " ds.day AS yearMonth,"
+ " COALESCE(SUM(COALESCE(ts.normalConsumed, 0) + COALESCE(ts.otConsumed, 0)), 0) AS 'TotalManhourConsumed',"
+ " :teamId AS teamId"
+ " FROM date_series ds"
+ " LEFT JOIN ("
+ " SELECT"
+ " DATE_FORMAT(ts.recordDate, '%Y-%m-%d') AS recordDate,"
+ " ts.staffID,"
+ " SUM(ts.normalConsumed) AS normalConsumed,"
+ " SUM(ts.otConsumed) AS otConsumed"
+ " FROM staff st"
+ " LEFT JOIN timesheet ts"
+ " on ts.staffID = st.id"
+ " WHERE ts.recordDate BETWEEN DATE_FORMAT(:startdate, '%Y-%m-%d') AND DATE_FORMAT(DATE_ADD(:startdate, INTERVAL 6 DAY), '%Y-%m-%d')"
+ " and st.teamId = :teamId"
+ " GROUP BY DATE_FORMAT(ts.recordDate, '%Y-%m-%d'), ts.staffID"
+ " ) ts ON ds.day = ts.recordDate"
+ " GROUP BY ds.day"
+ " ORDER BY ds.day;"

)

return jdbcDao.queryForList(sql.toString(), args)
@@ -1346,6 +1667,7 @@ open class DashboardService(
+ " AND dates.missing_date = ts.recordDate"
+ " WHERE"
+ " st.teamId = :teamId"
+ " and st.deleted = 0"
+ " AND ts.recordDate IS NULL"
+ " GROUP BY st.id, st.name"
+ " ORDER BY"
@@ -1595,6 +1917,7 @@ open class DashboardService(
+ " group by p.id, p.name"
+ " ) as result on result.pid = p2.id"
+ " where s2.id = :staffId"
+ " and result.manhours > 0"
+ " group by p2.id, p2.name, result.manhours"
)

@@ -1626,6 +1949,7 @@ open class DashboardService(
+ " group by p.id, p.name"
+ " ) as result on result.pid = p2.id"
+ " where s2.id = :staffId"
+ " and result.manhours > 0"
+ " group by p2.id, p2.name, result.manhours"
)

@@ -1656,6 +1980,7 @@ open class DashboardService(
+ " group by p.id, p.name"
+ " ) as result on result.pid = p2.id"
+ " where s2.id = :staffId"
+ " and result.manhours > 0"
+ " group by p2.id, p2.name, result.manhours"
)



+ 11
- 0
src/main/java/com/ffii/tsms/modules/data/service/SubsidiaryService.kt 파일 보기

@@ -38,6 +38,17 @@ open class SubsidiaryService(
return subsidiaryRepository.findByCode(code);
}

open fun createSubsidiaryCode(): String {
val prefix = "SY"

val latestSubsidiaryCode = subsidiaryRepository.getLatestCodeNumber()

if (latestSubsidiaryCode != null) {
return "$prefix-" + String.format("%03d", latestSubsidiaryCode + 1L)
} else {
return "$prefix-001"
}
}
open fun saveSubsidiary(saveSubsidiary: SaveSubsidiaryRequest): SaveSubsidiaryResponse {

val duplicateSubsidiary = findSubsidiaryByCode(saveSubsidiary.code)


+ 24
- 0
src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt 파일 보기

@@ -51,6 +51,7 @@ class DashboardController(
fun searchCustomerSubsidiaryProject(request: HttpServletRequest?): List<Map<String, Any>> {
val customerId = request?.getParameter("customerId")
val subsidiaryId = request?.getParameter("subsidiaryId")
val tableSorting = request?.getParameter("tableSorting")
val args = mutableMapOf<String, Any>()
var result: List<Map<String, Any>> = emptyList()
if (customerId != null) {
@@ -59,6 +60,9 @@ class DashboardController(
if (subsidiaryId != null) {
args["subsidiaryId"] = subsidiaryId
}
if (tableSorting != null) {
args["tableSorting"] = tableSorting
}

if (customerId != null && subsidiaryId != null) {
result = dashboardService.searchCustomerSubsidiaryProject(args)
@@ -80,16 +84,36 @@ class DashboardController(
@GetMapping("/searchTeamProject")
fun searchTeamProject(request: HttpServletRequest?): List<Map<String, Any>> {
val teamLeadId = request?.getParameter("teamLeadId")
val tableSorting = request?.getParameter("tableSorting")
val args = mutableMapOf<String, Any>()
var result: List<Map<String, Any>> = emptyList()
if (teamLeadId != null) {
args["teamLeadId"] = teamLeadId
}
if (tableSorting != null) {
args["tableSorting"] = tableSorting
}

result = dashboardService.searchTeamProject(args)

return result
}
@GetMapping("/searchTeamConsumption")
fun searchTeamConsumption(request: HttpServletRequest?): List<Map<String, Any>> {
val args = mutableMapOf<String, Any>()
val teamIdList = request?.getParameter("teamIdList")
val tableSorting = request?.getParameter("tableSorting")
val teamIds = teamIdList?.split(",")?.map { it.toInt() }?.toList()
var result: List<Map<String, Any>> = emptyList()
if (teamIds != null) {
args["teamIds"] = teamIds
}
if (tableSorting != null) {
args["tableSorting"] = tableSorting
}
result = dashboardService.searchTeamConsumption(args)
return result
}
@GetMapping("/searchFinancialSummaryCard")
fun searchFinancialSummaryCard(request: HttpServletRequest?): List<Map<String, Any>> {
val authority = dashboardService.viewDashboardAuthority()


+ 1
- 1
src/main/java/com/ffii/tsms/modules/data/web/SkillController.kt 파일 보기

@@ -19,7 +19,7 @@ import org.springframework.web.bind.annotation.*
open class SkillController(private val skillService: SkillService) {

@PostMapping("/save")
@PreAuthorize("hasAuthority('MAINTAIN_MASTERDATA')")
// @PreAuthorize("hasAuthority('MAINTAIN_MASTERDATA')")
open fun saveSkill(@Valid @RequestBody newSkill: NewSkillRequest): Skill {
return skillService.saveOrUpdate(newSkill)
}


+ 2
- 0
src/main/java/com/ffii/tsms/modules/project/entity/InvoiceRepository.kt 파일 보기

@@ -12,4 +12,6 @@ interface InvoiceRepository : AbstractRepository<Invoice, Long> {
fun findInvoiceInfoByPaidAmountIsNotNull(): List<InvoiceInfo>

fun findByInvoiceNo(invoiceNo: String): Invoice

fun findAllByProjectCodeAndPaidAmountIsNotNull(projectCode: String): List<Invoice>
}

+ 3
- 0
src/main/java/com/ffii/tsms/modules/project/entity/Project.kt 파일 보기

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

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

@ManyToOne
@JoinColumn(name = "serviceTypeId")
open var serviceType: ServiceType? = null


+ 2
- 0
src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt 파일 보기

@@ -34,4 +34,6 @@ interface ProjectRepository : AbstractRepository<Project, Long> {
fun findAllByStatusIsNotAndMainProjectIsNull(status: String): List<Project>

fun findAllByTeamLeadAndCustomer(teamLead: Staff, customer: Customer): List<Project>

fun findByCode(code: String): Project?
}

+ 4
- 4
src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt 파일 보기

@@ -362,7 +362,7 @@ open class InvoiceService(

// Check the import invoice with the data in DB
for (i in 2..sheet.lastRowNum){
val sheetInvoice = ExcelUtils.getCell(sheet, i, 0).stringCellValue
val sheetInvoice = ExcelUtils.getCell(sheet, i, 0).toString()
val sheetProjectCode = ExcelUtils.getCell(sheet, i, 1).stringCellValue
checkInvoiceNo(sheetInvoice, invoices, invoicesResult, true)
checkProjectCode(sheetProjectCode, projects, newProjectCodes)
@@ -398,7 +398,7 @@ open class InvoiceService(
// val paymentMilestoneId = getMilestonePaymentId(ExcelUtils.getCell(sheet, i, 1).stringCellValue, ExcelUtils.getCell(sheet, i, 5).stringCellValue)
// val milestonePayment = milestonePaymentRepository.findById(paymentMilestoneId).orElseThrow()
val invoice = Invoice().apply {
invoiceNo = ExcelUtils.getCell(sheet, i, 0).stringCellValue
invoiceNo = ExcelUtils.getCell(sheet, i, 0).toString()
projectCode = ExcelUtils.getCell(sheet, i, 1).stringCellValue
projectName = ExcelUtils.getCell(sheet, i, 2).stringCellValue
team = ExcelUtils.getCell(sheet, i, 3).stringCellValue
@@ -447,7 +447,7 @@ open class InvoiceService(
val duplicateItemsInInvoice = checkDuplicateItemInImportedInvoice(sheet,2,0)

for (i in 2..sheet.lastRowNum){
val sheetInvoice = ExcelUtils.getCell(sheet, i, 0).stringCellValue
val sheetInvoice = ExcelUtils.getCell(sheet, i, 0).toString()
val sheetProjectCode = ExcelUtils.getCell(sheet, i, 1).stringCellValue
checkInvoiceNo(sheetInvoice, invoices, invoicesResult, false)
checkProjectCode(sheetProjectCode, projects, newProjectCodes)
@@ -489,7 +489,7 @@ open class InvoiceService(
}

for (i in 2..sheet.lastRowNum){
val invoice = getInvoiceByInvoiceNo(ExcelUtils.getCell(sheet, i, 0).stringCellValue)
val invoice = getInvoiceByInvoiceNo(ExcelUtils.getCell(sheet, i, 0).toString())
invoice.paidAmount = ExcelUtils.getCell(sheet, i, 5).numericCellValue.toBigDecimal()
invoice.receiptDate = ExcelUtils.getCell(sheet, i, 4).dateCellValue.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()
saveAndFlush(invoice)


+ 344
- 25
src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt 파일 보기

@@ -1,26 +1,26 @@
package com.ffii.tsms.modules.project.service

import com.ffii.core.utils.ExcelUtils
import com.ffii.tsms.modules.common.SecurityUtils
import com.ffii.tsms.modules.data.entity.*
import com.ffii.tsms.modules.data.service.CustomerContactService
import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo
import com.ffii.tsms.modules.data.service.CustomerService
import com.ffii.tsms.modules.data.service.GradeService
import com.ffii.tsms.modules.data.service.SubsidiaryContactService
import com.ffii.tsms.modules.data.service.*
import com.ffii.tsms.modules.project.entity.*
import com.ffii.tsms.modules.project.entity.Milestone
import com.ffii.tsms.modules.project.entity.projections.InvoiceInfoSearchInfo
import com.ffii.tsms.modules.project.entity.projections.InvoiceSearchInfo
import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo
import com.ffii.tsms.modules.project.web.models.*
import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository
import org.apache.commons.logging.LogFactory
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.ss.usermodel.Workbook
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.Optional
import kotlin.jvm.optionals.getOrElse
import kotlin.jvm.optionals.getOrNull


@Service
open class ProjectsService(
private val projectRepository: ProjectRepository,
@@ -46,7 +46,11 @@ open class ProjectsService(
private val timesheetRepository: TimesheetRepository,
private val taskTemplateRepository: TaskTemplateRepository,
private val subsidiaryContactService: SubsidiaryContactService,
private val subsidiaryContactRepository: SubsidiaryContactRepository
private val subsidiaryContactRepository: SubsidiaryContactRepository,
private val teamRepository: TeamRepository,
private val customerRepository: CustomerRepository,
private val subsidiaryRepository: SubsidiaryRepository,
private val customerSubsidiaryService: CustomerSubsidiaryService,
) {
open fun allProjects(): List<ProjectSearchInfo> {
return projectRepository.findProjectSearchInfoByOrderByCreatedDesc()
@@ -107,7 +111,8 @@ open class ProjectsService(
code = project.code!!,
name = project.name!!,
status = project.status,
tasks = projectTaskRepository.findAllByProject(project).mapNotNull { pt -> pt.task },
tasks = projectTaskRepository.findAllByProject(project).mapNotNull { pt -> pt.task }
.sortedBy { it.taskGroup?.id },
milestones = milestoneRepository.findAllByProject(project)
.filter { milestone -> milestone.taskGroup?.id != null }
.associateBy { milestone -> milestone.taskGroup!!.id!! }.mapValues { (_, milestone) ->
@@ -164,10 +169,15 @@ open class ProjectsService(
.orElseThrow() else null
val teamLead = staffRepository.findById(request.projectLeadId).orElseThrow()
val customer = customerService.findCustomer(request.clientId)
val subsidiaryContact = subsidiaryContactRepository.findById(request.clientContactId).orElse(null)
val clientContact = customerContactService.findByContactId(request.clientContactId)
val subsidiaryContact =
if (request.clientContactId != null) subsidiaryContactRepository.findById(request.clientContactId)
.orElse(null) else null
val clientContact =
if (request.clientContactId != null) customerContactService.findByContactId(request.clientContactId) else null
val customerSubsidiary = request.clientSubsidiaryId?.let { subsidiaryService.findSubsidiary(it) }
val mainProject = if (request.mainProjectId != null && request.mainProjectId > 0) projectRepository.findById(request.mainProjectId).orElse(null) else null
val mainProject =
if (request.mainProjectId != null && request.mainProjectId > 0) projectRepository.findById(request.mainProjectId)
.orElse(null) else null

val allTasksMap = tasksService.allTasks().associateBy { it.id }
val taskGroupMap = tasksService.allTaskGroups().associateBy { it.id }
@@ -179,15 +189,23 @@ open class ProjectsService(
project.apply {
name = request.projectName
description = request.projectDescription
code = if (this.code.isNullOrEmpty() && request.mainProjectId == null) createProjectCode(request.isClpProject, project) else if (this.code.isNullOrEmpty() && request.mainProjectId != null && mainProject != null) createSubProjectCode(mainProject, project) else this.code
code = request.projectCode
?: if (this.code.isNullOrEmpty() && request.mainProjectId == null) createProjectCode(
request.isClpProject,
project
) else if (this.code.isNullOrEmpty() && request.mainProjectId != null && mainProject != null) createSubProjectCode(
mainProject,
project
) else this.code
expectedTotalFee = request.expectedProjectFee
subContractFee = request.subContractFee
totalManhour = request.totalManhour
actualStart = request.projectActualStart
actualEnd = request.projectActualEnd
status = if (this.status == "Deleted" || this.deleted == true) "Deleted"
else if (this.actualStart != null && this.actualEnd != null) "Completed"
else if (this.actualStart != null) "On-going"
else "Pending To Start"
else request.projectStatus ?: "Pending To Start"
isClpProject = request.isClpProject
this.mainProject = mainProject

@@ -203,11 +221,11 @@ open class ProjectsService(
this.teamLead = teamLead
this.customer = customer
custLeadName =
if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact.name else subsidiaryContact.name
if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact?.name else subsidiaryContact?.name
custLeadEmail =
if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact.email else subsidiaryContact.email
if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact?.email else subsidiaryContact?.email
custLeadPhone =
if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact.phone else subsidiaryContact.phone
if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact?.phone else subsidiaryContact?.phone
this.customerSubsidiary = customerSubsidiary
this.customerContact = if (customerSubsidiary == null) clientContact else null
this.subsidiaryContact = if (customerSubsidiary != null) subsidiaryContact else null
@@ -279,14 +297,15 @@ open class ProjectsService(
if (milestones.isNotEmpty()) {
project.apply {
planStart = milestones.mapNotNull { it.startDate }.minOrNull()
planEnd = milestones.mapNotNull { it.endDate }.maxOrNull()
planEnd = request.projectPlanEnd ?: milestones.mapNotNull { it.endDate }.maxOrNull()
}
}

val savedProject = projectRepository.save(project)
val savedProject = projectRepository.saveAndFlush(project)

val milestonesToDelete = milestoneRepository.findAllByProject(project).subtract(milestones.toSet())
val tasksToDelete = projectTaskRepository.findAllByProject(project).subtract(tasksToSave.toSet())
val mapTasksToSave = tasksToSave.map { it.task!!.id }
val tasksToDelete = projectTaskRepository.findAllByProject(project).filter { !mapTasksToSave.contains(it.task!!.id) }
val gradeAllocationsToDelete =
gradeAllocationRepository.findByProject(project).subtract(gradeAllocations.toSet())
milestoneRepository.deleteAll(milestonesToDelete)
@@ -386,7 +405,8 @@ open class ProjectsService(
)
})
},
expectedProjectFee = it.expectedTotalFee
expectedProjectFee = it.expectedTotalFee,
subContractFee = it.subContractFee
)
}
}
@@ -422,8 +442,9 @@ open class ProjectsService(
val subsidiaryContact = project.customerSubsidiary?.id?.let { subsidiaryId ->
subsidiaryContactService.findAllBySubsidiaryId(subsidiaryId)
} ?: emptyList()
val customerContact = project.customer?.id?.let { customerId -> customerContactService.findAllByCustomerId(customerId) }
?: emptyList()
val customerContact =
project.customer?.id?.let { customerId -> customerContactService.findAllByCustomerId(customerId) }
?: emptyList()

MainProjectDetails(
projectId = project.id,
@@ -441,10 +462,308 @@ open class ProjectsService(
buildingTypeIds = project.buildingTypes.mapNotNull { buildingType -> buildingType.id },
workNatureIds = project.workNatures.mapNotNull { workNature -> workNature.id },
clientId = project.customer?.id,
clientContactId = subsidiaryContact.find { contact -> contact.name == project.custLeadName }?.id ?: customerContact.find { contact -> contact.name == project.custLeadName }?.id,
clientContactId = subsidiaryContact.find { contact -> contact.name == project.custLeadName }?.id
?: customerContact.find { contact -> contact.name == project.custLeadName }?.id,
clientSubsidiaryId = project.customerSubsidiary?.id,
expectedProjectFee = project.expectedTotalFee
expectedProjectFee = project.expectedTotalFee,
subContractFee = project.subContractFee,
)
}
}

@Transactional(rollbackFor = [Exception::class])
open fun importFile(workbook: Workbook?): String {
val logger = LogFactory.getLog(javaClass)

if (workbook == null) {
return "No Excel import" // if workbook is null
}

val sheet: Sheet = workbook.getSheetAt(0)

for (i in 2..<sheet.lastRowNum) {
val row = sheet.getRow(i)

if (row?.getCell(0) != null && !row.getCell(0).stringCellValue.isNullOrBlank()) {
logger.info("row :$i | lastCellNum" + row.lastCellNum)

// process client
logger.info("---------client-------")
logger.info(customerRepository.findByName(row.getCell(5).stringCellValue.trim()))
val tempClient = customerRepository.findByName(row.getCell(5).stringCellValue.trim())
val currentClient =
if (tempClient.isPresent) tempClient.orElseThrow() else customerService.saveCustomer(
SaveCustomerRequest(
code = customerService.createClientCode(),
name = ExcelUtils.getStringValue(row.getCell(5)),
typeId = ExcelUtils.getIntValue(row.getCell(6)).toLong(),
deleteSubsidiaryIds = mutableListOf(),
deleteContactIds = mutableListOf(),
addSubsidiaryIds = mutableListOf(),
addContacts = mutableListOf(),
address = null,
brNo = null,
id = null,
district = null,
)
).customer
val clientId = currentClient.id!!

logger.info("customer: $clientId")

// process project
logger.info("---------project-------")
val projectTeamLead = teamRepository.findByCode(ExcelUtils.getStringValue(row.getCell(4))).staff

var mainProjectId: Long? = null
var projectCode = StringBuilder(row.getCell(0).stringCellValue).insert(1, '-').toString()

if (projectCode.contains('(')) {
val splitProjectCode = projectCode.split('(')
val splitMainProjectCode = splitProjectCode[0].split('-')

logger.info("splitProjectCode: " + splitProjectCode[1].split(')')[0])
projectCode =
splitMainProjectCode[0] + '-' + String.format(
"%04d",
splitMainProjectCode[1].toInt()
) + '-' + String.format("%03d", splitProjectCode[1].split(')')[0].toInt())

val mainProject =
projectRepository.findByCode(splitProjectCode[0]) ?: projectRepository.saveAndFlush(
Project().apply {
name = row.getCell(1).stringCellValue
description = row.getCell(1).stringCellValue
code = splitMainProjectCode[0] + '-' + String.format(
"%04d",
splitMainProjectCode[1].toInt()
)
status = "Completed"
projectCategory = projectCategoryRepository.findById(1).orElseThrow()
customer = currentClient
teamLead = projectTeamLead
})

mainProjectId = mainProject.id
} else {
val splitProjectCode = projectCode.split('-')
projectCode = splitProjectCode[0] + '-' + String.format("%04d", splitProjectCode[1].toInt())
}

val project = projectRepository.findByCode(projectCode)
val projectId = project?.id
logger.info("projectCode :$projectCode")

// process subsidiary
logger.info("---------subsidiary-------")
val subsidiary = if (row.getCell(7) != null && !row.getCell(7).stringCellValue.isNullOrBlank()) {
val tempSubsidiary = subsidiaryRepository.findByName(ExcelUtils.getStringValue(row.getCell(7)))

if (tempSubsidiary.isPresent) {
tempSubsidiary.orElseThrow()
} else {
subsidiaryRepository.findByName(ExcelUtils.getStringValue(row.getCell(7))).orElse(
subsidiaryService.saveSubsidiary(
SaveSubsidiaryRequest(
code = subsidiaryService.createSubsidiaryCode(),
name = ExcelUtils.getStringValue(row.getCell(7)),
typeId = ExcelUtils.getIntValue(row.getCell(8)).toLong(),
brNo = null,
address = null,
district = null,
deleteContactIds = mutableListOf(),
addContacts = mutableListOf(),
deleteCustomerIds = mutableListOf(),
addCustomerIds = mutableListOf(clientId),
id = null
)
).subsidiary
)
}
} else null

if (subsidiary != null) {
if (!customerSubsidiaryService.findAllCustomerIdsBySubsidiaryId(subsidiary.id!!)
.contains(clientId)
) {
subsidiaryService.saveSubsidiary(
SaveSubsidiaryRequest(
code = subsidiary.code,
name = subsidiary.name,
typeId = subsidiary.subsidiaryType.id!!,
brNo = subsidiary.brNo,
address = subsidiary.address,
district = subsidiary.district,
deleteContactIds = mutableListOf(),
addContacts = mutableListOf(),
deleteCustomerIds = mutableListOf(),
addCustomerIds = mutableListOf(clientId),
id = subsidiary.id
)
)
}
}

// process building type
logger.info("---------building type-------")
var buildingType = buildingTypeRepository.findByName(row.getCell(13).stringCellValue)
if (buildingType == null) {
buildingType =
buildingTypeRepository.saveAndFlush(BuildingType().apply {
name = row.getCell(13).stringCellValue
})
}

// process work nature
logger.info("---------work nature-------")
var workNature = workNatureRepository.findByName(row.getCell(14).stringCellValue)
if (workNature == null) {
workNature =
workNatureRepository.saveAndFlush(WorkNature().apply { name = row.getCell(14).stringCellValue })
}

// process staff - staff from column R (17)
logger.info("---------staff-------")
val allocatedStaffIds = mutableListOf<Long>()
for (j in 18..<row.lastCellNum step 2) {
logger.info("j: $j | staffId: " + row.getCell(j).stringCellValue)
val tempStaffId = row.getCell(j).stringCellValue

if (!tempStaffId.isNullOrBlank()) {
allocatedStaffIds.add(staffRepository.findByStaffId(tempStaffId).orElseThrow().id!!)
}
}

// get task template - TW-Full QS - stage 5
logger.info("---------task template-------")
val taskTemplate = taskTemplateRepository.findById(3).orElseThrow()
val tasks = taskTemplate.tasks.groupBy { it.taskGroup!!.id }
val groups = taskTemplate.groupAllocations.associateBy(
keySelector = { it.taskGroup!!.id!! },
valueTransform = { it.percentage!! }
)
val projectTasks = if (project != null) projectTaskRepository.findAllByProject(project) else mutableListOf()
var groupedProjectTasks = mapOf<Long, List<Long>>()
if (projectTasks.isNotEmpty()) {
groupedProjectTasks = projectTasks.groupBy {
it.task!!.taskGroup!!.id!!
}.mapValues { (_, projectTask) ->
projectTask.map { it.task!!.id!! }
}
}
val taskGroups = mutableMapOf<Long, TaskGroupAllocation>()
groups.entries.forEach{ (key, value) ->
var taskIds = tasks[key]!!.map { it.id!! }
if (groupedProjectTasks.isNotEmpty() && !groupedProjectTasks[key].isNullOrEmpty()) {
taskIds = (taskIds + groupedProjectTasks[key]!!).distinct()
}
if (key == 5L) {
taskGroups[key] = TaskGroupAllocation(taskIds.plus(41).distinct(), value)
logger.info("taskGroups[${key}]: ${taskGroups[key]}")
} else {
taskGroups[key] = TaskGroupAllocation(taskIds, value)
logger.info("taskGroups[${key}]: ${taskGroups[key]}")
}
}

logger.info("---------Import-------")
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val startDate = ExcelUtils.getDateValue(
row.getCell(2),
DateTimeFormatter.ofPattern("MM/dd/yyyy")
).format(formatter)
val endDate = ExcelUtils.getDateValue(
row.getCell(3),
DateTimeFormatter.ofPattern("MM/dd/yyyy")
).format(formatter)
saveProject(
NewProjectRequest(
projectCode = projectCode,
projectName = row.getCell(1).stringCellValue,
projectDescription = row.getCell(1).stringCellValue,
projectActualStart = ExcelUtils.getDateValue(
row.getCell(2),
DateTimeFormatter.ofPattern("MM/dd/yyyy")
),
projectPlanEnd = ExcelUtils.getDateValue(
row.getCell(3),
DateTimeFormatter.ofPattern("MM/dd/yyyy")
),
projectLeadId = projectTeamLead.id!!,
clientId = clientId,
clientSubsidiaryId = subsidiary?.id,
expectedProjectFee = row.getCell(9).numericCellValue,
subContractFee = null,
totalManhour = row.getCell(11).numericCellValue,
locationId = 1, // HK
buildingTypeIds = mutableListOf(buildingType!!.id!!),
workNatureIds = mutableListOf(workNature!!.id!!),
serviceTypeId = 9,
fundingTypeId = row.getCell(16).numericCellValue.toLong(),
allocatedStaffIds = allocatedStaffIds,
isClpProject = projectCode[0] != 'M',
mainProjectId = mainProjectId,
projectCategoryId = 1,
contractTypeId = if (row.getCell(14).stringCellValue == "Maintenance Term Contract") 5 else 2,
projectId = projectId,
projectStatus = "On-going",
clientContactId = null,
isSubsidiaryContact = subsidiary?.id != null && subsidiary.id!! > 0,
manhourPercentageByGrade = taskTemplate.gradeAllocations.associateBy(
keySelector = { it.grade!!.id!! },
valueTransform = { it.percentage!! }
),
projectActualEnd = null,
taskTemplateId = taskTemplate.id,
taskGroups = taskGroups,
milestones = mapOf(
Pair(
1, com.ffii.tsms.modules.project.web.models.Milestone(
startDate = startDate,
endDate = endDate,
payments = mutableListOf()
)
),
Pair(
2, com.ffii.tsms.modules.project.web.models.Milestone(
startDate = startDate,
endDate = endDate,
payments = mutableListOf()
)
),
Pair(
3, com.ffii.tsms.modules.project.web.models.Milestone(
startDate = startDate,
endDate = endDate,
payments = mutableListOf()
)
),
Pair(
4, com.ffii.tsms.modules.project.web.models.Milestone(
startDate = startDate,
endDate = endDate,
payments = mutableListOf()
)
),
Pair(
5, com.ffii.tsms.modules.project.web.models.Milestone(
startDate = startDate,
endDate = endDate,
payments = mutableListOf(
PaymentInputs(
-1, "Manhour Import", ExcelUtils.getDateValue(
row.getCell(2),
DateTimeFormatter.ofPattern("MM/dd/yyyy")
).toString(), row.getCell(9).numericCellValue
)
)
)
),
))
)
}
}

return if (sheet.lastRowNum > 0) "Import Excel success" else "Import Excel failure"
}
}

+ 24
- 2
src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt 파일 보기

@@ -2,15 +2,21 @@ package com.ffii.tsms.modules.project.web

import com.ffii.core.exception.NotFoundException
import com.ffii.tsms.modules.data.entity.*
import com.ffii.tsms.modules.project.entity.Project
import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo
import com.ffii.tsms.modules.project.entity.ProjectCategory
import com.ffii.tsms.modules.project.entity.ProjectRepository
import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo
import com.ffii.tsms.modules.project.service.ProjectsService
import com.ffii.tsms.modules.project.web.models.*
import jakarta.servlet.http.HttpServletRequest
import jakarta.validation.Valid
import org.apache.poi.ss.usermodel.Workbook
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.ServletRequestBindingException
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartHttpServletRequest


@RestController
@RequestMapping("/projects")
@@ -85,4 +91,20 @@ class ProjectsController(private val projectsService: ProjectsService, private v
fun projectWorkNatures(): List<WorkNature> {
return projectsService.allWorkNatures()
}

@PostMapping("/import")
@Throws(ServletRequestBindingException::class)
fun importFile(request: HttpServletRequest): ResponseEntity<*> {
var workbook: Workbook? = null

try {
val multipartFile = (request as MultipartHttpServletRequest).getFile("multipartFileList")
workbook = XSSFWorkbook(multipartFile?.inputStream)
} catch (e: Exception) {
println("Excel Wrong")
println(e)
}

return ResponseEntity.ok(projectsService.importFile(workbook))
}
}

+ 2
- 1
src/main/java/com/ffii/tsms/modules/project/web/models/EditProjectDetails.kt 파일 보기

@@ -40,5 +40,6 @@ data class EditProjectDetails(
val milestones: Map<Long, Milestone>,

// Miscellaneous
val expectedProjectFee: Double?
val expectedProjectFee: Double?,
val subContractFee: Double?
)

+ 1
- 0
src/main/java/com/ffii/tsms/modules/project/web/models/MainProjectDetails.kt 파일 보기

@@ -27,4 +27,5 @@ data class MainProjectDetails (
val clientSubsidiaryId: Long?,

val expectedProjectFee: Double?,
val subContractFee: Double?,
)

+ 6
- 3
src/main/java/com/ffii/tsms/modules/project/web/models/NewProjectRequest.kt 파일 보기

@@ -7,7 +7,7 @@ import java.time.LocalDate
data class NewProjectRequest(
// Project details
// @field:NotBlank(message = "project code cannot be empty")
// val projectCode: String,
val projectCode: String?,
@field:NotBlank(message = "project name cannot be empty")
val projectName: String,
val projectCategoryId: Long,
@@ -16,8 +16,10 @@ data class NewProjectRequest(
val projectId: Long?,
val projectActualStart: LocalDate?,
val projectActualEnd: LocalDate?,
val projectPlanEnd: LocalDate?,
val isClpProject: Boolean?,
val mainProjectId: Long?,
val projectStatus: String?,

val serviceTypeId: Long,
val fundingTypeId: Long,
@@ -30,7 +32,7 @@ data class NewProjectRequest(

// Client details
val clientId: Long,
val clientContactId: Long,
val clientContactId: Long?,
val clientSubsidiaryId: Long?,

// Allocation
@@ -43,7 +45,8 @@ data class NewProjectRequest(
val milestones: Map<Long, Milestone>,

// Miscellaneous
val expectedProjectFee: Double
val expectedProjectFee: Double,
val subContractFee: Double?
)

data class TaskGroupAllocation(


+ 476
- 158
src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt 파일 보기

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

import com.ffii.core.support.JdbcDao
import com.ffii.tsms.modules.data.entity.Customer
import com.ffii.tsms.modules.data.entity.Grade
import com.ffii.tsms.modules.data.entity.Salary
import com.ffii.tsms.modules.data.entity.Staff
import com.ffii.tsms.modules.data.entity.Team
@@ -17,6 +18,8 @@ import org.apache.poi.ss.util.CellRangeAddress
import org.apache.poi.ss.util.CellUtil
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.apache.poi.ss.usermodel.FormulaEvaluator
import org.apache.poi.ss.util.CellReference
import org.apache.poi.ss.util.RegionUtil
import org.springframework.core.io.ClassPathResource
import org.springframework.stereotype.Service
import java.io.ByteArrayOutputStream
@@ -54,6 +57,7 @@ open class ReportService(
private val COMPLETE_PROJECT_OUTSTANDING_RECEIVABLE =
"templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx"
private val COMPLETION_PROJECT = "templates/report/AR05_Project Completion Report.xlsx"
private val CROSS_TEAM_CHARGE_REPORT = "templates/report/Cross Team Charge Report.xlsx"

// ==============================|| GENERATE REPORT ||============================== //
fun generalCreateReportIndexed( // just loop through query records one by one, return rowIndex
@@ -65,7 +69,7 @@ open class ReportService(
var rowIndex = startRow
var columnIndex = startColumn
result.forEachIndexed { index, obj ->
var tempCell = sheet.getRow(rowIndex).createCell(columnIndex)
var tempCell = (sheet.getRow(rowIndex) ?: sheet.createRow(rowIndex)).createCell(columnIndex)
tempCell.setCellValue((index + 1).toDouble())
val keys = obj.keys.toList()
keys.forEachIndexed { keyIndex, key ->
@@ -299,6 +303,25 @@ open class ReportService(
return outputStream.toByteArray()
}

@Throws(IOException::class)
fun generateCrossTeamChargeReport(
month: String,
timesheets: List<Timesheet>,
teams: List<Team>,
grades: List<Grade>,
): ByteArray {
// Generate the Excel report with query results
val workbook: Workbook =
createCrossTeamChargeReport(month, timesheets, teams, grades, CROSS_TEAM_CHARGE_REPORT)

// Write the workbook to a ByteArrayOutputStream
val outputStream: ByteArrayOutputStream = ByteArrayOutputStream()
workbook.write(outputStream)
workbook.close()

return outputStream.toByteArray()
}

// ==============================|| CREATE REPORT ||============================== //

// EX01 Financial Report
@@ -435,7 +458,8 @@ open class ReportService(
val uninvoiceCell = row.createCell(12)
uninvoiceCell.apply {
cellFormula =
" IF(I${rowNum}<=J${rowNum}, I${rowNum}-L${rowNum}, IF(AND(I${rowNum}>J${rowNum}, J${rowNum}<L${rowNum}), 0, IF(AND(I${rowNum}>J${rowNum}, J${rowNum}>=L${rowNum}), J${rowNum}-L${rowNum}, 0))) "
" IF(H${rowNum}-L${rowNum}<0, 0, H${rowNum}-L${rowNum})"
// " IF(I${rowNum}<=J${rowNum}, I${rowNum}-L${rowNum}, IF(AND(I${rowNum}>J${rowNum}, J${rowNum}<L${rowNum}), 0, IF(AND(I${rowNum}>J${rowNum}, J${rowNum}>=L${rowNum}), J${rowNum}-L${rowNum}, 0))) "
cellStyle.dataFormat = accountingStyle
}

@@ -528,6 +552,11 @@ open class ReportService(
CellUtil.setCellStyleProperty(sumUInvoiceCell, "borderBottom", BorderStyle.DOUBLE)

val lastCpiCell = row.createCell(13)
lastCpiCell.apply {
cellFormula = "SUM(N15:N${rowNum})"
cellStyle = boldFontCellStyle
cellStyle.dataFormat = accountingStyle
}
CellUtil.setCellStyleProperty(lastCpiCell, "borderTop", BorderStyle.THIN)
CellUtil.setCellStyleProperty(lastCpiCell, "borderBottom", BorderStyle.DOUBLE)

@@ -606,7 +635,7 @@ open class ReportService(
val row9: Row = sheet.getRow(rowNum)
val cell5 = row9.createCell(2)
cell5.apply {
cellFormula = "N${lastRowNum}"
cellFormula = "M${lastRowNum}"
cellStyle.dataFormat = accountingStyle
}

@@ -691,12 +720,12 @@ open class ReportService(
rowIndex = 10
sheet.getRow(rowIndex).apply {
createCell(1).apply {
setCellValue(project.expectedTotalFee!! * 0.8)
setCellValue(if (project.expectedTotalFee != null) project.expectedTotalFee!! * 0.8 else 0.0)
cellStyle.dataFormat = accountingStyle
}

createCell(2).apply {
setCellValue(project.expectedTotalFee!!)
setCellValue(project.expectedTotalFee ?: 0.0)
cellStyle.dataFormat = accountingStyle
}
}
@@ -979,10 +1008,15 @@ open class ReportService(
project.milestones.forEach { milestone: Milestone ->

val manHoursSpent = groupedTimesheets[Pair(project.id, milestone.id)]?.sum() ?: 0.0
val resourceUtilization = manHoursSpent / (milestone.stagePercentAllocation!! / 100 * project.totalManhour!!)
val resourceUtilization =
manHoursSpent / (milestone.stagePercentAllocation!! / 100 * project.totalManhour!!)
// logger.info(project.name + " : " + milestone.taskGroup?.name + " : " + ChronoUnit.DAYS.between(LocalDate.now(), milestone.endDate))
// logger.info(daysUntilCurrentStageEnd)
if (ChronoUnit.DAYS.between(LocalDate.now(), milestone.endDate) <= daysUntilCurrentStageEnd.toLong() && resourceUtilization <= resourceUtilizationPercentage.toDouble() / 100.0) {
if (ChronoUnit.DAYS.between(
LocalDate.now(),
milestone.endDate
) <= daysUntilCurrentStageEnd.toLong() && resourceUtilization <= resourceUtilizationPercentage.toDouble() / 100.0
) {
milestoneCount++
val tempRow = sheet.getRow(rowIndex) ?: sheet.createRow(rowIndex)
rowIndex++
@@ -1004,7 +1038,7 @@ open class ReportService(
//
// val resourceUtilization =
// manHoursSpent / (milestone.stagePercentAllocation!! / 100 * project.totalManhour!!)
setCellValue(resourceUtilization)
setCellValue(resourceUtilization)
// } else {
// setCellValue(0.0)
// }
@@ -1080,6 +1114,13 @@ open class ReportService(
val boldFont = workbook.createFont()
boldFont.bold = true
boldStyle.setFont(boldFont)

val projectsStyle = workbook.createCellStyle()
val projectsFont = workbook.createFont()
projectsFont.bold = true
projectsFont.fontName = "Times New Roman"
projectsStyle.setFont(projectsFont)

val daysOfMonth = (1..month.lengthOfMonth()).map { day ->
val date = month.withDayOfMonth(day)
val formattedDate = date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"))
@@ -1228,7 +1269,7 @@ open class ReportService(
projectList.forEachIndexed { index, title ->
tempCell = sheet.getRow(7).createCell(columnIndex + index)
tempCell.setCellValue(title)
tempCell.cellStyle = boldStyle
tempCell.cellStyle = projectsStyle
CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER)
CellUtil.setVerticalAlignment(tempCell, VerticalAlignment.CENTER)
CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom)
@@ -1271,7 +1312,7 @@ open class ReportService(
///////////////////////////////////////////////////////// Leave Hours title ////////////////////////////////////////////////////////////////////
tempCell = sheet.getRow(rowIndex).createCell(columnIndex)
tempCell.setCellValue("Leave Hours")
tempCell.cellStyle = boldStyle
tempCell.cellStyle = projectsStyle
CellUtil.setVerticalAlignment(tempCell, VerticalAlignment.CENTER)
CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER)
CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom)
@@ -1279,8 +1320,10 @@ open class ReportService(
columnIndex += 1
tempCell = sheet.getRow(rowIndex).createCell(columnIndex)
tempCell.setCellValue("Daily Manhour Spent\n(Excluding Leave Hours)")
tempCell.cellStyle = boldStyle
CellUtil.setAlignment(tempCell, HorizontalAlignment.LEFT)
tempCell.cellStyle = projectsStyle
// CellUtil.setAlignment(tempCell, HorizontalAlignment.LEFT)
CellUtil.setVerticalAlignment(tempCell, VerticalAlignment.CENTER)
CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER)
CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom)

sheet.addMergedRegion(CellRangeAddress(6, 6, 2, columnIndex))
@@ -1391,7 +1434,7 @@ open class ReportService(
rowIndex = 5
columnIndex = 0

generalCreateReportIndexed(sheet, result, rowIndex, columnIndex)
generalCreateReportIndexed(sheet, result.distinct(), rowIndex, columnIndex)
return workbook
}

@@ -1481,7 +1524,7 @@ open class ReportService(
setDataAndConditionalFormatting(workbook, sheet, lateStartData, evaluator)

// Automatically adjust column widths to fit content
autoSizeColumns(sheet)
autoSizeColumns(sheet)

return workbook
}
@@ -1627,7 +1670,8 @@ open class ReportService(
// NEW Column F: Subsidiary Name
val subsidiaryNameCell = row.createCell(5)
// subsidiaryNameCell.setCellValue(data["subsidiary_name"] as String)
val subsidiaryName = data["subsidiary_name"] as? String ?: "N/A" // Checks if subsidiary_name is null and replaces it with "N/A"
val subsidiaryName =
data["subsidiary_name"] as? String ?: "N/A" // Checks if subsidiary_name is null and replaces it with "N/A"
subsidiaryNameCell.setCellValue(subsidiaryName)

// Column G: Project Plan Start Date
@@ -1748,136 +1792,139 @@ open class ReportService(
)
return jdbcDao.queryForList(sql.toString(), args)
}

open fun getProjectCompletionReport(args: Map<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder("select"
+ " result.code, "
+ " result.name, "
+ " result.teamCode, "
+ " result.custCode, "
+ " result.subCode, " )
val sql = StringBuilder(
"select"
+ " result.code, "
+ " result.name, "
+ " result.teamCode, "
+ " result.custCode, "
+ " result.subCode, "
)
if (args.get("outstanding") as Boolean) {
sql.append(" result.projectFee - COALESCE(i.paidAmount, 0) as `Receivable Remained`, ")
// sql.append(" result.projectFee - (result.totalBudget - COALESCE(i.issueAmount , 0) + COALESCE(i.issueAmount, 0) - COALESCE(i.paidAmount, 0)) as `Receivable Remained`, ")
}
sql.append(
" DATE_FORMAT(result.actualEnd, '%d/%m/%Y') as actualEnd "
+ " from ( "
+ " SELECT "
+ " pt.project_id, "
+ " min(p.code) as code, "
+ " min(p.name) as name, "
+ " min(t.code) as teamCode, "
+ " concat(min(c.code), ' - ', min(c.name)) as custCode, "
+ " COALESCE(concat(min(ss.code),' - ',min(ss.name)), 'N/A') as subCode, "
+ " min(p.actualEnd) as actualEnd, "
+ " min(p.expectedTotalFee) as projectFee, "
+ " sum(COALESCE(tns.totalConsumed*sal.hourlyRate, 0)) as totalBudget "
+ " FROM ( "
+ " SELECT "
+ " t.staffId, "
+ " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as totalConsumed, "
+ " t.projectTaskId AS taskId "
+ " FROM timesheet t "
+ " LEFT JOIN staff s ON t.staffId = s.id "
+ " LEFT JOIN team te on s.teamId = te.id "
+ " GROUP BY t.staffId, t.projectTaskId "
+ " order by t.staffId "
+ " ) AS tns "
+ " right join project_task pt ON tns.taskId = pt.id "
+ " left join project p on p.id = pt.project_id "
+ " left JOIN staff s ON p.teamLead = s.id "
+ " left join salary sal on s.salaryId = sal.salaryPoint "
+ " left JOIN team t ON s.teamId = t.id "
+ " left join customer c on c.id = p.customerId "
+ " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id "
+ " where p.deleted = false ")
if (args.containsKey("teamId")) {
sql.append("t.id = :teamId")
}
sql.append(
+ " from ( "
+ " SELECT "
+ " pt.project_id, "
+ " min(p.code) as code, "
+ " min(p.name) as name, "
+ " min(t.code) as teamCode, "
+ " concat(min(c.code), ' - ', min(c.name)) as custCode, "
+ " COALESCE(concat(min(ss.code),' - ',min(ss.name)), 'N/A') as subCode, "
+ " min(p.actualEnd) as actualEnd, "
+ " min(p.expectedTotalFee) as projectFee, "
+ " sum(COALESCE(tns.totalConsumed*sal.hourlyRate, 0)) as totalBudget "
+ " FROM ( "
+ " SELECT "
+ " t.staffId, "
+ " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as totalConsumed, "
+ " t.projectTaskId AS taskId "
+ " FROM timesheet t "
+ " LEFT JOIN staff s ON t.staffId = s.id "
+ " LEFT JOIN team te on s.teamId = te.id "
+ " GROUP BY t.staffId, t.projectTaskId "
+ " order by t.staffId "
+ " ) AS tns "
+ " right join project_task pt ON tns.taskId = pt.id "
+ " left join project p on p.id = pt.project_id "
+ " left JOIN staff s ON p.teamLead = s.id "
+ " left join salary sal on s.salaryId = sal.salaryPoint "
+ " left JOIN team t ON s.teamId = t.id "
+ " left join customer c on c.id = p.customerId "
+ " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id "
+ " where p.deleted = false "
)
if (args.containsKey("teamId")) {
sql.append("t.id = :teamId")
}
sql.append(
" and p.status = 'Completed' "
+ " and p.actualEnd BETWEEN :startDate and :endDate "
+ " group by pt.project_id "
+ " ) as result "
+ " left join invoice i on result.code = i.projectCode "
+ " order by result.actualEnd ")
+ " and p.actualEnd BETWEEN :startDate and :endDate "
+ " group by pt.project_id "
+ " ) as result "
+ " left join invoice i on result.code = i.projectCode "
+ " order by result.actualEnd "
)

return jdbcDao.queryForList(sql.toString(), args)
}

open fun getProjectResourceOverconsumptionReport(args: Map<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder("WITH teamNormalConsumed AS ("
+ " SELECT "
+ " s.teamId, "
+ " pt.project_id, "
+ " SUM(tns.totalConsumed) AS totalConsumed, "
+ " sum(tns.totalBudget) as totalBudget "
+ " FROM ( "
+ " SELECT "
+ " t.staffId, "
+ " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as totalConsumed, "
+ " t.projectTaskId AS taskId, "
+ " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) * min(sal.hourlyRate) as totalBudget "
+ " FROM timesheet t "
+ " LEFT JOIN staff s ON t.staffId = s.id "
+ " left join salary sal on sal.salaryPoint = s.salaryId "
+ " GROUP BY t.staffId, t.projectTaskId "
+ " ) AS tns "
+ " INNER JOIN project_task pt ON tns.taskId = pt.id "
+ " JOIN staff s ON tns.staffId = s.id "
+ " JOIN team t ON s.teamId = t.id "
+ " GROUP BY teamId, project_id "
+ " ) "
+ " SELECT "
+ " p.code, "
+ " p.name, "
+ " t.code as team, "
+ " concat(c.code, ' - ', c.name) as client, "
+ " COALESCE(concat(ss.code, ' - ', ss.name), 'N/A') as subsidiary, "
+ " p.expectedTotalFee * 0.8 as plannedBudget, "
+ " COALESCE(tns.totalBudget, 0) as actualConsumedBudget, "
+ " COALESCE(p.totalManhour, 0) as plannedManhour, "
+ " COALESCE(tns.totalConsumed, 0) as actualConsumedManhour, "
+ " (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) as budgetConsumptionRate, "
+ " (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) as manhourConsumptionRate, "
+ " CASE "
+ " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 "
+ " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1 "
+ " then 'Potential Overconsumption' "
+ " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 "
+ " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1 "
+ " then 'Overconsumption' "
+ " else 'Within Budget' "
+ " END as status "
+ " FROM project p "
+ " LEFT JOIN team t ON p.teamLead = t.teamLead "
+ " LEFT JOIN staff s ON p.teamLead = s.id "
+ " LEFT JOIN salary sa ON s.salaryId = sa.salaryPoint "
+ " LEFT JOIN customer c ON p.customerId = c.id "
+ " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id "
+ " left join teamNormalConsumed tns on tns.project_id = p.id "
+ " WHERE p.deleted = false "
+ " and p.status = 'On-going' "
val sql = StringBuilder(
"WITH teamNormalConsumed AS ("
+ " SELECT"
+ " tns.project_id,"
+ " SUM(tns.totalConsumed) AS totalConsumed, "
+ " sum(tns.totalBudget) as totalBudget "
+ " FROM ( "
+ " SELECT"
+ " t.staffId,"
+ " t.projectId AS project_id,"
+ " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as totalConsumed, "
+ " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) * min(sal.hourlyRate) as totalBudget "
+ " FROM timesheet t"
+ " LEFT JOIN staff s ON t.staffId = s.id "
+ " left join salary sal on sal.salaryPoint = s.salaryId "
+ " GROUP BY t.staffId, t.projectId"
+ " ) AS tns"
+ " GROUP BY project_id"
+ " ) "
+ " SELECT "
+ " p.code, "
+ " p.name, "
+ " t.code as team, "
+ " concat(c.code, ' - ', c.name) as client, "
+ " COALESCE(concat(ss.code, ' - ', ss.name), 'N/A') as subsidiary, "
+ " p.expectedTotalFee * 0.8 as plannedBudget, "
+ " COALESCE(tns.totalBudget, 0) as actualConsumedBudget, "
+ " COALESCE(p.totalManhour, 0) as plannedManhour, "
+ " COALESCE(tns.totalConsumed, 0) as actualConsumedManhour, "
+ " (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) as budgetConsumptionRate, "
+ " (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) as manhourConsumptionRate, "
+ " CASE "
+ " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 "
+ " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1 "
+ " then 'Overconsumption' "
+ " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 "
+ " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1 "
+ " then 'Potential Overconsumption' "
+ " else 'Within Budget' "
+ " END as status "
+ " FROM project p "
+ " LEFT JOIN team t ON p.teamLead = t.teamLead "
+ " LEFT JOIN staff s ON p.teamLead = s.id "
+ " LEFT JOIN salary sa ON s.salaryId = sa.salaryPoint "
+ " LEFT JOIN customer c ON p.customerId = c.id "
+ " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id "
+ " left join teamNormalConsumed tns on tns.project_id = p.id "
+ " WHERE p.deleted = false "
+ " and p.status = 'On-going' "
)
if (args != null) {
var statusFilter: String = ""
if (args.containsKey("teamId"))
sql.append("and t.id = :teamId")
sql.append(" and t.id = :teamId")
if (args.containsKey("custId"))
sql.append("and c.id = :custId")
sql.append(" and c.id = :custId")
if (args.containsKey("subsidiaryId"))
sql.append("and ss.id = :subsidiaryId")
sql.append(" and ss.id = :subsidiaryId")
if (args.containsKey("status"))
statusFilter = when (args.get("status")) {
"Potential Overconsumption" -> "and " +
"Potential Overconsumption" -> " and " +
" (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit " +
" and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " +
" or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit " +
" and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1 "
"All" -> "and " +

"All" -> " and " +
" (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit " +
" or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit "
// "Overconsumption" -> "and " +
// "Overconsumption" -> " and " +
// " ((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " +
// " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1) "

@@ -1900,6 +1947,15 @@ open class ReportService(
+ " left join salary s2 on s.salaryId = s2.salaryPoint"
+ " left join team t2 on t2.id = s.teamId"
+ " ),"
+ " cte_timesheet_sum as ("
+ " Select p.code, sum((IFNULL(t.normalConsumed, 0) + IFNULL(t.otConsumed , 0)) * s2.hourlyRate) as sumManhourExpenditure"
+ " from timesheet t"
+ " left join project_task pt on pt.id = t.projectTaskId"
+ " left join project p ON p.id = pt.project_id"
+ " left join staff s on s.id = t.staffId"
+ " left join salary s2 on s.salaryId = s2.salaryPoint"
+ " group by p.code"
+ " ),"
+ " cte_invoice as ("
+ " select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount"
+ " from invoice i"
@@ -1909,10 +1965,11 @@ open class ReportService(
+ " select p.code, p.description, c.name as client, IFNULL(s2.name, \"N/A\") as subsidiary, concat(t.code, \" - \", t.name) as teamLead,"
+ " IFNULL(cte_ts.normalConsumed, 0) as normalConsumed, IFNULL(cte_ts.otConsumed, 0) as otConsumed, DATE_FORMAT(cte_ts.recordDate, '%Y-%m') as recordDate, "
+ " IFNULL(cte_ts.salaryPoint, 0) as salaryPoint, "
+ " IFNULL(cte_ts.hourlyRate, 0) as hourlyRate, IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount, IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount,"
+ " IFNULL(cte_ts.hourlyRate, 0) as hourlyRate, IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount, IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount, IFNULL(cte_tss.sumManhourExpenditure, 0) as sumManhourExpenditure,"
+ " s.name, s.staffId, g.code as gradeCode, g.name as gradeName, t2.code as teamCode, t2.name as teamName"
+ " from project p"
+ " left join cte_timesheet cte_ts on p.code = cte_ts.code"
+ " left join cte_timesheet_sum cte_tss on p.code = cte_tss.code"
+ " left join customer c on c.id = p.customerId"
+ " left join tsmsdb.team t on t.teamLead = p.teamLead"
+ " left join cte_invoice cte_i on cte_i.code = p.code"
@@ -1975,6 +2032,9 @@ open class ReportService(
if (info["code"] == item["code"] && "subsidiary" !in info) {
info["subsidiary"] = item.getValue("subsidiary")
}
if (info["manhourExpenditure"] != item.getValue("sumManhourExpenditure")) {
info["manhourExpenditure"] = item.getValue("sumManhourExpenditure")
}
if (info["description"] != item.getValue("description")) {
info["description"] = item.getValue("description")
}
@@ -2162,9 +2222,9 @@ open class ReportService(
rowNum = 6
val row6: Row = sheet.getRow(rowNum)
val row6Cell = row6.getCell(1)
val clientSubsidiary = if(info.getValue("subsidiary").toString() != "N/A"){
val clientSubsidiary = if (info.getValue("subsidiary").toString() != "N/A") {
info.getValue("subsidiary").toString()
}else{
} else {
info.getValue("client").toString()
}
row6Cell.setCellValue(clientSubsidiary)
@@ -2335,7 +2395,8 @@ open class ReportService(
}
val totalManhourECell = totalManhourERow.getCell(1) ?: totalManhourERow.createCell(1)
totalManhourECell.apply {
cellFormula = "SUM(${lastColumnIndex}${startRow}:${lastColumnIndex}${startRow + staffInfoList.size})"
// cellFormula = "SUM(${lastColumnIndex}${startRow}:${lastColumnIndex}${startRow + staffInfoList.size})"
setCellValue(info.getValue("manhourExpenditure") as Double)
cellStyle.dataFormat = accountingStyle
}
CellUtil.setCellStyleProperty(totalManhourETitleCell, "borderBottom", BorderStyle.THIN)
@@ -2358,10 +2419,10 @@ open class ReportService(
return workbook
}

fun getCostAndExpense(clientId: Long?, teamId: Long?, type: String): List<Map<String,Any?>>{
fun getCostAndExpense(clientId: Long?, teamId: Long?, type: String): List<Map<String, Any?>> {
val sql = StringBuilder(
" with cte_timesheet as ( "
+ " Select p.code, s.name as staff, IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.salaryPoint, s2.hourlyRate, t.staffId,"
+ " Select p.code, s.name as staff, IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.salaryPoint, s2.hourlyRate, t.staffId,"
+ " t.recordDate"
+ " from timesheet t"
+ " left join project_task pt on pt.id = t.projectTaskId"
@@ -2385,20 +2446,20 @@ open class ReportService(
+ " left join team t2 on t2.id = s.teamId"
+ " where ISNULL(p.code) = False"
)
if(clientId != null){
if(type == "client"){
if (clientId != null) {
if (type == "client") {
sql.append(
" and c.id = :clientId "
)
}
if(type == "subsidiary"){
if (type == "subsidiary") {
sql.append(
" and s2.id = :clientId "
)
}
}

if(teamId != null){
if (teamId != null) {
sql.append(
" and p.teamLead = :teamId "
)
@@ -2417,9 +2478,9 @@ open class ReportService(
val queryList = jdbcDao.queryForList(sql.toString(), args)
val costAndExpenseList = mutableListOf<Map<String, Any?>>()

for(item in queryList){
for (item in queryList) {
val hourlyRate = (item.getValue("hourlyRate") as BigDecimal).toDouble()
if(item["code"] !in costAndExpenseList){
if (item["code"] !in costAndExpenseList.map { it["code"] }) {
costAndExpenseList.add(
mapOf(
"code" to item["code"],
@@ -2428,22 +2489,27 @@ open class ReportService(
"subsidiary" to item["subsidiary"],
"teamLead" to item["teamLead"],
"budget" to item["expectedTotalFee"],
"totalManhours" to item["normalConsumed"] as Double + item["otConsumed"] as Double,
"manhourExpenditure" to (hourlyRate * item["normalConsumed"] as Double )
+ (hourlyRate * item["otConsumed"]as Double * otFactor)
"totalManhours" to item["normalConsumed"] as Double + item["otConsumed"] as Double,
"manhourExpenditure" to (hourlyRate * item["normalConsumed"] as Double)
+ (hourlyRate * item["otConsumed"] as Double * otFactor)
)
)
}else{
} else {
val existingMap = costAndExpenseList.find { it.containsValue(item["code"]) }!!
costAndExpenseList[costAndExpenseList.indexOf(existingMap)] = existingMap.toMutableMap().apply {
put("totalManhours", get("manhours") as Double + (item["normalConsumed"] as Double + item["otConsumed"] as Double))
put("manhourExpenditure", get("manhourExpenditure") as Double + ((hourlyRate * item["normalConsumed"] as Double )
+ (hourlyRate * item["otConsumed"]as Double * otFactor)))
put(
"totalManhours",
get("totalManhours") as Double + (item["normalConsumed"] as Double + item["otConsumed"] as Double)
)
put(
"manhourExpenditure",
get("manhourExpenditure") as Double + ((hourlyRate * item["normalConsumed"] as Double)
+ (hourlyRate * item["otConsumed"] as Double * otFactor))
)
}
}
}
val result = costAndExpenseList.map {
item ->
val result = costAndExpenseList.map { item ->
val budget = (item["budget"] as? Double)?.times(0.8) ?: 0.0
val budgetRemain = budget - (item["manhourExpenditure"] as? Double ?: 0.0)
val remainingPercent = (budgetRemain / budget)
@@ -2462,7 +2528,7 @@ open class ReportService(
clientId: Long?,
budgetPercentage: Double?,
type: String
): Workbook{
): Workbook {
val resource = ClassPathResource(templatePath)
val templateInputStream = resource.inputStream
val workbook: Workbook = XSSFWorkbook(templateInputStream)
@@ -2480,9 +2546,9 @@ open class ReportService(
rowNum = 2
val row2: Row = sheet.getRow(rowNum)
val row2Cell = row2.getCell(2)
if(teamId == null){
if (teamId == null) {
row2Cell.setCellValue("All")
}else{
} else {
val sql = StringBuilder(
" select t.id, t.code, t.name, concat(t.code, \" - \" ,t.name) as teamLead from team t where t.id = :teamId "
)
@@ -2493,16 +2559,16 @@ open class ReportService(
rowNum = 3
val row3: Row = sheet.getRow(rowNum)
val row3Cell = row3.getCell(2)
if(clientId == null){
if (clientId == null) {
row3Cell.setCellValue("All")
}else{
val sql= StringBuilder()
if(type == "client"){
} else {
val sql = StringBuilder()
if (type == "client") {
sql.append(
" select c.id, c.name from customer c where c.id = :clientId "
)
}
if(type == "subsidiary"){
if (type == "subsidiary") {
sql.append(
" select s.id, s.name from subsidiary s where s.id = :clientId "
)
@@ -2513,15 +2579,15 @@ open class ReportService(


val filterList: List<Map<String, Any?>>
if(budgetPercentage != null){
if (budgetPercentage != null) {
filterList = costAndExpenseList.filter { ((it["budgetPercentage"] as? Double) ?: 0.0) <= budgetPercentage }
}else{
} else {
filterList = costAndExpenseList
}


rowNum = 6
for(item in filterList){
for (item in filterList) {
val index = filterList.indexOf(item)
val row: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum)
val cell = row.getCell(0) ?: row.createCell(0)
@@ -2571,13 +2637,13 @@ open class ReportService(

val cell8 = row.getCell(8) ?: row.createCell(8)
cell8.apply {
cellFormula = "G${rowNum+1}-H${rowNum+1}"
cellFormula = "G${rowNum + 1}-H${rowNum + 1}"
}
CellUtil.setCellStyleProperty(cell8, "dataFormat", accountingStyle)

val cell9 = row.getCell(9) ?: row.createCell(9)
cell9.apply {
cellFormula = "I${rowNum+1}/G${rowNum+1}"
cellFormula = "I${rowNum + 1}/G${rowNum + 1}"
}
CellUtil.setCellStyleProperty(cell9, "dataFormat", percentStyle)

@@ -2587,11 +2653,18 @@ open class ReportService(
return workbook
}

fun genCostAndExpenseReport(request: costAndExpenseRequest): ByteArray{
fun genCostAndExpenseReport(request: costAndExpenseRequest): ByteArray {

val costAndExpenseList = getCostAndExpense(request.clientId, request.teamId, request.type)

val workbook: Workbook = createCostAndExpenseWorkbook(COSTANDEXPENSE_REPORT, costAndExpenseList, request.teamId, request.clientId, request.budgetPercentage, request.type)
val workbook: Workbook = createCostAndExpenseWorkbook(
COSTANDEXPENSE_REPORT,
costAndExpenseList,
request.teamId,
request.clientId,
request.budgetPercentage,
request.type
)

val outputStream: ByteArrayOutputStream = ByteArrayOutputStream()
workbook.write(outputStream)
@@ -2599,8 +2672,16 @@ open class ReportService(

return outputStream.toByteArray()
}
open fun getLateStartDetails(teamId: Long?, clientId: Long?, remainedDate: LocalDate, remainedDateTo: LocalDate, type: String?): List<Map<String, Any>> {
val sql = StringBuilder("""

open fun getLateStartDetails(
teamId: Long?,
clientId: Long?,
remainedDate: LocalDate,
remainedDateTo: LocalDate,
type: String?
): List<Map<String, Any>> {
val sql = StringBuilder(
"""
SELECT
p.code AS project_code,
p.name AS project_name,
@@ -2621,7 +2702,8 @@ open class ReportService(
p.status = 'Pending to Start'
AND p.planStart < CURRENT_DATE
AND m.endDate BETWEEN :remainedDate AND :remainedDateTo
""".trimIndent())
""".trimIndent()
)

if (teamId != null && teamId > 0) {
sql.append(" AND t.id = :teamId")
@@ -2645,4 +2727,240 @@ open class ReportService(
)
return jdbcDao.queryForList(sql.toString(), args)
}
}

@Throws(IOException::class)
private fun createCrossTeamChargeReport(
month: String,
timesheets: List<Timesheet>,
teams: List<Team>,
grades: List<Grade>,
templatePath: String,
): Workbook {
// please create a new function for each report template
val resource = ClassPathResource(templatePath)
val templateInputStream = resource.inputStream
val workbook: Workbook = XSSFWorkbook(templateInputStream)

val sheet: Sheet = workbook.getSheetAt(0)

// accounting style + comma style
val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)")

// bold font with border style
val boldFont = workbook.createFont().apply {
bold = true
fontName = "Times New Roman"
}

val boldFontWithBorderStyle = workbook.createCellStyle().apply {
setFont(boldFont)
borderTop = BorderStyle.THIN
borderBottom = BorderStyle.THIN
borderLeft = BorderStyle.THIN
borderRight = BorderStyle.THIN
}

// normal font
val normalFont = workbook.createFont().apply {
fontName = "Times New Roman"
}

val normalFontWithBorderStyle = workbook.createCellStyle().apply {
setFont(normalFont)
borderTop = BorderStyle.THIN
borderBottom = BorderStyle.THIN
borderLeft = BorderStyle.THIN
borderRight = BorderStyle.THIN
}

var rowIndex = 1 // Assuming the location is in (1,2), which is the report date field
var columnIndex = 2
val monthFormat = DateTimeFormatter.ofPattern("MMMM yyyy", Locale.ENGLISH)
val reportMonth = YearMonth.parse(month, DateTimeFormatter.ofPattern("yyyy-MM"))
val convertReportMonth = YearMonth.of(reportMonth.year, reportMonth.month).format(monthFormat)
sheet.getRow(rowIndex).createCell(columnIndex).apply {
setCellValue(convertReportMonth)
}

// if (timesheets.isNotEmpty()) {
val combinedTeamCodeColNumber = grades.size
val sortedGrades = grades.sortedBy { it.id }
val sortedTeams = teams.sortedBy { it.id }

val groupedTimesheets = timesheets
.filter { it.project?.teamLead?.team?.id != it.staff?.team?.id }
.groupBy { timesheetEntry ->
Triple(
timesheetEntry.project?.teamLead?.team?.id,
timesheetEntry.staff?.team?.id,
timesheetEntry.staff?.grade?.id
)
}
.mapValues { (_, timesheetEntries) ->
timesheetEntries.map { timesheet ->
if (timesheet.normalConsumed != null) {
mutableMapOf<String, Double>().apply {
this["manHour"] = timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0)
this["salary"] = timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0)
.times(timesheet.staff!!.salary.hourlyRate.toDouble())
}
} else if (timesheet.otConsumed != null) {
mutableMapOf<String, Double>().apply {
this["manHour"] = timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0)
this["salary"] = timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0)
.times(timesheet.staff!!.salary.hourlyRate.toDouble())
}
} else {
mutableMapOf<String, Double>().apply {
this["manHour"] = 0.0
this["salary"] = 0.0
}
}
}
}

if (sortedTeams.isNotEmpty() && sortedTeams.size > 1) {
rowIndex = 3
sortedTeams.forEach { team: Team ->

// Team
sheet.createRow(rowIndex++).apply {
createCell(0).apply {
setCellValue("Team to be charged:")
cellStyle = boldFontWithBorderStyle
CellUtil.setAlignment(this, HorizontalAlignment.LEFT)
}

val rangeAddress = CellRangeAddress(this.rowNum, this.rowNum, 1, 2 + combinedTeamCodeColNumber)
sheet.addMergedRegion(rangeAddress)
RegionUtil.setBorderTop(BorderStyle.THIN, rangeAddress, sheet)
RegionUtil.setBorderLeft(BorderStyle.THIN, rangeAddress, sheet)
RegionUtil.setBorderRight(BorderStyle.THIN, rangeAddress, sheet)
RegionUtil.setBorderBottom(BorderStyle.THIN, rangeAddress, sheet)

createCell(1).apply {
setCellValue(team.code)
cellStyle = normalFontWithBorderStyle
CellUtil.setAlignment(this, HorizontalAlignment.CENTER)
}

}

// Grades
sheet.createRow(rowIndex++).apply {
columnIndex = 0
createCell(columnIndex++).apply {
setCellValue("")
cellStyle = boldFontWithBorderStyle
}


sortedGrades.forEach { grade: Grade ->
createCell(columnIndex++).apply {
setCellValue(grade.name)
cellStyle = boldFontWithBorderStyle
CellUtil.setAlignment(this, HorizontalAlignment.CENTER)
}
}

createCell(columnIndex++).apply {
setCellValue("Total Manhour by Team")
cellStyle = boldFontWithBorderStyle
}

createCell(columnIndex).apply {
setCellValue("Total Cost Adjusted by Salary Point by Team")
cellStyle = boldFontWithBorderStyle
}
}

// Team + Manhour
val startRow = rowIndex
var endRow = rowIndex
sortedTeams.forEach { chargedTeam: Team ->
if (team.id != chargedTeam.id) {
endRow++
sheet.createRow(rowIndex++).apply {
columnIndex = 0
createCell(columnIndex++).apply {
setCellValue(chargedTeam.code)
cellStyle = normalFontWithBorderStyle
CellUtil.setAlignment(this, HorizontalAlignment.CENTER)
}

var totalSalary = 0.0
sortedGrades.forEach { grade: Grade ->
createCell(columnIndex++).apply {
setCellValue(
groupedTimesheets[Triple(
team.id,
chargedTeam.id,
grade.id
)]?.sumOf { it.getValue("manHour") } ?: 0.0)

totalSalary += groupedTimesheets[Triple(
team.id,
chargedTeam.id,
grade.id
)]?.sumOf { it.getValue("salary") } ?: 0.0

cellStyle = normalFontWithBorderStyle.apply {
dataFormat = accountingStyle
}
}
}

createCell(columnIndex++).apply {
val lastCellLetter = CellReference.convertNumToColString(this.columnIndex - 1)
cellFormula = "sum(B${this.rowIndex + 1}:${lastCellLetter}${this.rowIndex + 1})"
cellStyle = boldFontWithBorderStyle.apply {
dataFormat = accountingStyle
}
}

createCell(columnIndex).apply {
setCellValue(totalSalary)
cellStyle = boldFontWithBorderStyle.apply {
dataFormat = accountingStyle
}
}
}
}
}

// Total Manhour by grade
sheet.createRow(rowIndex).apply {
columnIndex = 0
createCell(columnIndex++).apply {
setCellValue("Total Manhour by Grade")
cellStyle = boldFontWithBorderStyle
}

sortedGrades.forEach { grade: Grade ->
createCell(columnIndex++).apply {
val currentCellLetter = CellReference.convertNumToColString(this.columnIndex)
cellFormula = "sum(${currentCellLetter}${startRow}:${currentCellLetter}${endRow})"
cellStyle = normalFontWithBorderStyle.apply {
dataFormat = accountingStyle
}
}
}

createCell(columnIndex++).apply {
setCellValue("")
cellStyle = boldFontWithBorderStyle
}

createCell(columnIndex).apply {
setCellValue("")
cellStyle = boldFontWithBorderStyle
}
}
rowIndex += 2
}
}
// }

return workbook
}
}

+ 40
- 2
src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt 파일 보기

@@ -34,8 +34,12 @@ import java.time.format.DateTimeFormatter
import com.ffii.tsms.modules.project.entity.Project
import com.ffii.tsms.modules.project.service.SubsidiaryService
import com.ffii.tsms.modules.report.web.model.*
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.springframework.data.domain.Example
import org.springframework.data.domain.ExampleMatcher
import java.time.YearMonth
import java.util.*

@RestController
@RequestMapping("/reports")
@@ -55,9 +59,11 @@ class ReportController(
private val customerService: CustomerService,
private val subsidiaryService: SubsidiaryService,
private val invoiceService: InvoiceService, private val gradeAllocationRepository: GradeAllocationRepository,
private val subsidiaryRepository: SubsidiaryRepository
private val subsidiaryRepository: SubsidiaryRepository, private val staffAllocationRepository: StaffAllocationRepository,
private val gradeRepository: GradeRepository
) {

private val logger: Log = LogFactory.getLog(javaClass)
@PostMapping("/fetchProjectsFinancialStatusReport")
@Throws(ServletRequestBindingException::class, IOException::class)
fun getFinancialStatusReport(@RequestBody @Valid request: FinancialStatusReportRequest): ResponseEntity<Resource> {
@@ -76,7 +82,8 @@ class ReportController(

val project = projectRepository.findById(request.projectId).orElseThrow()
val projectTasks = projectTaskRepository.findAllByProject(project)
val invoices = invoiceService.findAllByProjectAndPaidAmountIsNotNull(project)
// val invoices = invoiceService.findAllByProjectAndPaidAmountIsNotNull(project)
val invoices = invoiceRepository.findAllByProjectCodeAndPaidAmountIsNotNull(project.code!!)
val timesheets = timesheetRepository.findAllByProjectTaskIn(projectTasks)

val reportResult: ByteArray = excelReportService.generateProjectCashFlowReport(project, invoices, timesheets, request.dateType)
@@ -300,4 +307,35 @@ class ReportController(
.body(ByteArrayResource(reportResult))
}

@PostMapping("/CrossTeamChargeReport")
@Throws(ServletRequestBindingException::class, IOException::class)
fun getCrossTeamChargeReport(@RequestBody @Valid request: CrossTeamChargeReportRequest): ResponseEntity<Resource> {

val authorities = staffsService.currentAuthorities() ?: return ResponseEntity.noContent().build()

if (authorities.stream().anyMatch { it.authority.equals("GENERATE_REPORTS") }) {
val crossProjects = staffAllocationRepository.findAll()
.filter { it.project?.teamLead?.team?.id != it.staff?.team?.id }
.map { it.project!! }
.distinct()

val reportMonth = YearMonth.parse(request.month, DateTimeFormatter.ofPattern("yyyy-MM"))
val convertReportMonth = YearMonth.of(reportMonth.year, reportMonth.month)
val startDate = convertReportMonth.atDay(1)
val endDate = convertReportMonth.atEndOfMonth()
val timesheets = timesheetRepository.findAllByProjectIn(crossProjects)
.filter { it.recordDate != null &&
(it.recordDate!!.isEqual(startDate) || it.recordDate!!.isEqual(endDate) || (it.recordDate!!.isAfter(startDate) && it.recordDate!!.isBefore(endDate)))}
val teams = teamRepository.findAll().filter { it.deleted == false }
val grades = gradeRepository.findAll().filter { it.deleted == false }

val reportResult: ByteArray = excelReportService.generateCrossTeamChargeReport(request.month, timesheets, teams, grades)
return ResponseEntity.ok()
.header("filename", "Cross Team Charge Report - " + LocalDate.now() + ".xlsx")
.body(ByteArrayResource(reportResult))

} else {
return ResponseEntity.noContent().build()
}
}
}

+ 4
- 0
src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt 파일 보기

@@ -57,4 +57,8 @@ data class ProjectCompletionReport (
val startDate: LocalDate,
val endDate: LocalDate,
val outstanding: Boolean
)

data class CrossTeamChargeReportRequest (
val month: String,
)

+ 2
- 0
src/main/java/com/ffii/tsms/modules/timesheet/entity/TimesheetRepository.kt 파일 보기

@@ -14,6 +14,8 @@ interface TimesheetRepository : AbstractRepository<Timesheet, Long> {

fun findAllByProjectTaskIn(projectTasks: List<ProjectTask>): List<Timesheet>

fun findAllByProjectIn(project: List<Project>): List<Timesheet>

fun deleteAllByStaffAndRecordDate(staff: Staff, recordDate: LocalDate)

@Query("SELECT new com.ffii.tsms.modules.timesheet.entity.projections.TimesheetHours(IFNULL(SUM(normalConsumed), 0), IFNULL(SUM(otConsumed), 0)) FROM Timesheet t JOIN ProjectTask pt on t.projectTask = pt WHERE pt.project = ?1")


+ 125
- 24
src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt 파일 보기

@@ -1,17 +1,22 @@
package com.ffii.tsms.modules.timesheet.service

import com.ffii.core.exception.BadRequestException
import com.ffii.core.utils.ExcelUtils
import com.ffii.tsms.modules.data.entity.BuildingType
import com.ffii.tsms.modules.data.entity.Staff
import com.ffii.tsms.modules.data.entity.StaffRepository
import com.ffii.tsms.modules.data.entity.WorkNature
import com.ffii.tsms.modules.data.service.StaffsService
import com.ffii.tsms.modules.data.service.TeamService
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.project.entity.*
import com.ffii.tsms.modules.project.web.models.*
import com.ffii.tsms.modules.timesheet.entity.Timesheet
import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository
import com.ffii.tsms.modules.timesheet.web.models.TeamMemberTimeEntries
import com.ffii.tsms.modules.timesheet.web.models.TimeEntry
import org.apache.commons.logging.LogFactory
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.ss.usermodel.Workbook
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDate
@@ -40,14 +45,15 @@ open class TimesheetsService(
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) } }
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.otConsumed = timeEntry.otHours
this.projectTask = projectTask
this.projectTask = projectTask
this.project = project
this.remark = timeEntry.remark
}
@@ -60,7 +66,11 @@ open class TimesheetsService(
}

@Transactional
open fun saveMemberTimeEntry(staffId: Long, entry: TimeEntry, recordDate: LocalDate?): Map<String, List<TimeEntry>> {
open fun saveMemberTimeEntry(
staffId: Long,
entry: TimeEntry,
recordDate: LocalDate?
): Map<String, List<TimeEntry>> {
val authorities = staffsService.currentAuthorities() ?: throw BadRequestException()

if (!authorities.stream().anyMatch { it.authority.equals("MAINTAIN_TIMESHEET") }) {
@@ -75,11 +85,11 @@ open class TimesheetsService(
val timesheet = timesheetRepository.findById(entry.id).getOrDefault(Timesheet()).apply {
val task = entry.taskId?.let { taskRepository.findById(it).getOrNull() }
val project = entry.projectId?.let { projectRepository.findById(it).getOrNull() }
val projectTask = project?.let { p -> task?.let { t -> projectTaskRepository.findByProjectAndTask(p, t) } }
val projectTask = project?.let { p -> task?.let { t -> projectTaskRepository.findByProjectAndTask(p, t) } }

this.normalConsumed = entry.inputHours
this.otConsumed = entry.otHours
this.projectTask = projectTask
this.projectTask = projectTask
this.project = project
this.remark = entry.remark
this.recordDate = this.recordDate ?: recordDate
@@ -109,7 +119,7 @@ open class TimesheetsService(

open fun getTeamMemberTimesheet(): Map<Long, TeamMemberTimeEntries> {
val authorities = staffsService.currentAuthorities() ?: return emptyMap()
if (authorities.stream().anyMatch { it.authority.equals("MAINTAIN_TIMESHEET")}) {
if (authorities.stream().anyMatch { it.authority.equals("MAINTAIN_TIMESHEET") }) {
val currentStaff = staffsService.currentStaff()
// Get team where current staff is team lead

@@ -136,27 +146,118 @@ open class TimesheetsService(
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 ?: timesheet.project?.id,
taskId = timesheet.projectTask?.task?.id,
taskGroupId = timesheet.projectTask?.task?.taskGroup?.id,
inputHours = timesheet.normalConsumed ?: 0.0,
otHours = timesheet.otConsumed ?: 0.0,
remark = timesheet.remark
)
} }
.mapValues { (_, timesheets) ->
timesheets.map { timesheet ->
TimeEntry(
id = timesheet.id!!,
projectId = timesheet.projectTask?.project?.id ?: timesheet.project?.id,
taskId = timesheet.projectTask?.task?.id,
taskGroupId = timesheet.projectTask?.task?.taskGroup?.id,
inputHours = timesheet.normalConsumed ?: 0.0,
otHours = timesheet.otConsumed ?: 0.0,
remark = timesheet.remark
)
}
}
}

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 ?: 0.0) + (timeEntry.inputHours ?: 0.0),
otHours = (acc.otHours ?: 0.0) + (timeEntry.otHours ?: 0.0)
) }
timeEntries.reduce { acc, timeEntry ->
acc.copy(
inputHours = (acc.inputHours ?: 0.0) + (timeEntry.inputHours ?: 0.0),
otHours = (acc.otHours ?: 0.0) + (timeEntry.otHours ?: 0.0)
)
}
}
}

@Transactional(rollbackFor = [Exception::class])
open fun importFile(workbook: Workbook?): String {
val logger = LogFactory.getLog(javaClass)

if (workbook == null) {
return "No Excel import" // if workbook is null
}

val notExistProjectList = mutableListOf<String>()
val sheet: Sheet = workbook.getSheetAt(0)

logger.info("---------Start Import Timesheets-------")
val timesheetList = mutableListOf<Timesheet>().toMutableList();
for (i in 1..<sheet.lastRowNum) {
val row = sheet.getRow(i)

if (row?.getCell(0) != null && !row.getCell(0).stringCellValue.isNullOrBlank() && row.getCell(3) != null && !row.getCell(
3
).stringCellValue.isNullOrBlank()
) {
logger.info("row :$i | lastCellNum" + row.lastCellNum)

// process staff
logger.info("---------staff-------")
var staff = staffRepository.findByStaffId(row.getCell(0).stringCellValue).getOrNull()
if (staff == null) {
staff = staffRepository.findByStaffId("B000").orElseThrow()
}

// process project
logger.info("---------project-------")
var projectCode = StringBuilder(row.getCell(3).stringCellValue).insert(1, '-').toString()

if (row.getCell(4) != null && row.getCell(4).toString().isNotBlank()) {
val subCode = row.getCell(4).numericCellValue
val splitMainProjectCode = projectCode.split('-')
projectCode = splitMainProjectCode[0] + '-' + String.format(
"%04d",
splitMainProjectCode[1].toInt()
) + '-' + String.format("%03d", subCode.toInt())
} else {
val splitProjectCode = projectCode.split('-')
projectCode = splitProjectCode[0] + '-' + String.format("%04d", splitProjectCode[1].toInt())
}
logger.info("Project Code: $projectCode")

val project = projectRepository.findByCode(projectCode)

// process project task
logger.info("---------project task-------")
val task = taskRepository.findById(41).getOrNull()
val projectTask =
project?.let { p -> task?.let { t -> projectTaskRepository.findByProjectAndTask(p, t) } }

// process record date
logger.info("---------record date-------")
val formatter = DateTimeFormatter.ofPattern("dd-MMM-yyyy")
val recordDate = LocalDate.parse(row.getCell(6).toString(), formatter);

// normal hour + ot hour
logger.info("---------normal hour + ot hour-------")
val hours: Double = row.getCell(7).numericCellValue
val normalHours = if (hours > 8.0) 8.0 else hours
val otHours = if (hours > 8.0) hours - 8.0 else 0.0

if (project != null) {
timesheetList += Timesheet().apply {
this.staff = staff
this.recordDate = recordDate
this.normalConsumed = normalHours
this.otConsumed = otHours
this.projectTask = projectTask
this.project = project
}
} else {
notExistProjectList += projectCode
}
}
}

timesheetRepository.saveAll(timesheetList)
logger.info("---------end-------")
logger.info("Not Exist Project List: "+ notExistProjectList.distinct().joinToString(", "))

return if (sheet.lastRowNum > 0) "Import Excel success btw " + notExistProjectList.joinToString(", ") else "Import Excel failure"
}
}

+ 22
- 0
src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt 파일 보기

@@ -5,12 +5,18 @@ 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.*
import jakarta.servlet.http.HttpServletRequest
import jakarta.validation.Valid
import org.apache.poi.ss.usermodel.Workbook
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.ServletRequestBindingException
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 org.springframework.web.multipart.MultipartHttpServletRequest
import java.time.LocalDate
import java.time.format.DateTimeFormatter

@@ -85,4 +91,20 @@ class TimesheetsController(private val timesheetsService: TimesheetsService, pri
fun leaveTypes(): List<LeaveType> {
return leaveService.getLeaveTypes()
}

@PostMapping("/import")
@Throws(ServletRequestBindingException::class)
fun importFile(request: HttpServletRequest): ResponseEntity<*> {
var workbook: Workbook? = null

try {
val multipartFile = (request as MultipartHttpServletRequest).getFile("multipartFileList")
workbook = XSSFWorkbook(multipartFile?.inputStream)
} catch (e: Exception) {
println("Excel Wrong")
println(e)
}

return ResponseEntity.ok(timesheetsService.importFile(workbook))
}
}

+ 7
- 7
src/main/java/com/ffii/tsms/modules/user/service/UserService.java 파일 보기

@@ -229,6 +229,13 @@ public class UserService extends AbstractBaseEntityService<User, Long, UserRepos
List<Map<String, Integer>> authBatchDeleteValues = req.getRemoveAuthIds().stream()
.map(authId -> Map.of("userId", (int)id, "authId", authId))
.collect(Collectors.toList());
if (!authBatchDeleteValues.isEmpty()) {
jdbcDao.batchUpdate(
"DELETE FROM user_authority"
+ " WHERE userId = :userId ",
// + "AND authId = :authId",
authBatchDeleteValues);
}
if (!authBatchInsertValues.isEmpty()) {
jdbcDao.batchUpdate(
"INSERT IGNORE INTO user_authority (userId, authId)"
@@ -236,13 +243,6 @@ public class UserService extends AbstractBaseEntityService<User, Long, UserRepos
authBatchInsertValues);
}

if (!authBatchDeleteValues.isEmpty()) {
jdbcDao.batchUpdate(
"DELETE FROM user_authority"
+ " WHERE userId = :userId ",
// + "AND authId = :authId",
authBatchDeleteValues);
}
return instance;
}



+ 9
- 0
src/main/resources/db/changelog/changes/20240617_01_cyril/01_update_task.sql 파일 보기

@@ -0,0 +1,9 @@
-- liquibase formatted sql
-- changeset cyril:task

INSERT
INTO
task
(name, taskGroupId)
VALUES
('5.8 Manhour Import', 5);

+ 5
- 0
src/main/resources/db/changelog/changes/20240617_01_cyril/02_update_project.sql 파일 보기

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

ALTER TABLE `project`
CHANGE COLUMN `name` `name` VARCHAR(255) CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_unicode_ci' NOT NULL ;

+ 81
- 0
src/main/resources/db/changelog/changes/20240702_01_cyril/01_update_authority.sql 파일 보기

@@ -0,0 +1,81 @@
-- liquibase formatted sql
-- changeset cyril:authority, user_authority

UPDATE authority
SET name='Maintain User in Master Data'
WHERE id=1;
UPDATE authority
SET name='Maintain User Group in Master Data'
WHERE id=2;
UPDATE authority
SET name='View User in Master Data'
WHERE id=3;
UPDATE authority
SET name='View User Group in Master Data'
WHERE id=4;
DELETE FROM authority
WHERE id=5;
DELETE FROM authority
WHERE id=6;
INSERT INTO authority (authority,name)
VALUES ('VIEW_CLIENT','View Client in Master Data');
INSERT INTO authority (authority,name)
VALUES ('VIEW_SUBSIDIARY','View Subsidiary in Master Data');
INSERT INTO authority (authority,name)
VALUES ('VIEW_STAFF','View Staff in Master Data');
INSERT INTO authority (authority,name)
VALUES ('VIEW_COMPANY','View Company in Master Data');
INSERT INTO authority (authority,name)
VALUES ('VIEW_SKILL','View Skill in Master Data');
INSERT INTO authority (authority,name)
VALUES ('VIEW_DEPARTMENT','View Department in Master Data');
INSERT INTO authority (authority,name)
VALUES ('VIEW_POSITION','View Position in Master Data');
INSERT INTO authority (authority,name)
VALUES ('VIEW_SALARY','View Salary in Master Data');
INSERT INTO authority (authority,name)
VALUES ('VIEW_TEAM','View Team in Master Data');
INSERT INTO authority (authority,name)
VALUES ('VIEW_HOLIDAY','View Holiday in Master Data');
INSERT INTO authority (authority,name)
VALUES ('MAINTAIN_CLIENT','Maintain Client in Master Data');
INSERT INTO authority (authority,name)
VALUES ('MAINTAIN_SUBSIDIARY','Maintain Subsidiary in Master Data');
INSERT INTO authority (authority,name)
VALUES ('MAINTAIN_STAFF','Maintain Staff in Master Data');
INSERT INTO authority (authority,name)
VALUES ('MAINTAIN_COMPANY','Maintain Company in Master Data');
INSERT INTO authority (authority,name)
VALUES ('MAINTAIN_SKILL','Maintain Skill in Master Data');
INSERT INTO authority (authority,name)
VALUES ('MAINTAIN_DEPARTMENT','Maintain Department in Master Data');
INSERT INTO authority (authority,name)
VALUES ('MAINTAIN_POSITION','Maintain Position in Master Data');
INSERT INTO authority (authority,name)
VALUES ('MAINTAIN_SALARY','Maintain Salary in Master Data');
INSERT INTO authority (authority,name)
VALUES ('MAINTAIN_TEAM','Maintain Team in Master Data');
INSERT INTO authority (authority,name)
VALUES ('MAINTAIN_HOLIDAY','Maintain Holiday in Master Data');

INSERT INTO `user_authority` VALUES
(1,21),
(1,22),
(1,23),
(1,24),
(1,25),
(1,26),
(1,27),
(1,28),
(1,29),
(1,30),
(1,31),
(1,32),
(1,33),
(1,34),
(1,35),
(1,36),
(1,37),
(1,38),
(1,39),
(1,40);

+ 5
- 0
src/main/resources/db/changelog/changes/20240704_01_cyril/01_update_project.sql 파일 보기

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

ALTER TABLE `project`
ADD COLUMN `subContractFee` DOUBLE NULL DEFAULT NULL AFTER `expectedTotalFee`;

BIN
src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx 파일 보기


BIN
src/main/resources/templates/report/AR04_Cost and Expense Report v02.xlsx 파일 보기


BIN
src/main/resources/templates/report/AR05_Project Completion Report.xlsx 파일 보기


BIN
src/main/resources/templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx 파일 보기


BIN
src/main/resources/templates/report/AR08_Monthly Work Hours Analysis Report.xlsx 파일 보기


BIN
src/main/resources/templates/report/Cross Team Charge Report.xlsx 파일 보기


불러오는 중...
취소
저장