| @@ -3,4 +3,5 @@ package com.ffii.tsms.modules.data.entity; | |||||
| import com.ffii.core.support.AbstractRepository | import com.ffii.core.support.AbstractRepository | ||||
| interface BuildingTypeRepository : AbstractRepository<BuildingType, Long> { | interface BuildingTypeRepository : AbstractRepository<BuildingType, Long> { | ||||
| fun findByName(name: String): BuildingType? | |||||
| } | } | ||||
| @@ -3,6 +3,7 @@ package com.ffii.tsms.modules.data.entity; | |||||
| import java.util.Optional; | import java.util.Optional; | ||||
| import java.util.List; | import java.util.List; | ||||
| import org.springframework.data.jpa.repository.Query; | |||||
| import org.springframework.data.repository.query.Param; | import org.springframework.data.repository.query.Param; | ||||
| import com.ffii.core.support.AbstractRepository; | import com.ffii.core.support.AbstractRepository; | ||||
| @@ -11,4 +12,7 @@ public interface CustomerRepository extends AbstractRepository<Customer, Long> { | |||||
| List<Customer> findAllByDeletedFalse(); | List<Customer> findAllByDeletedFalse(); | ||||
| Optional<Customer> findByCode(@Param("code") String code); | Optional<Customer> findByCode(@Param("code") String code); | ||||
| Optional<Customer> findByName(@Param("name") String name); | Optional<Customer> findByName(@Param("name") String name); | ||||
| @Query("SELECT max(cast(substring_index(c.code, '-', -1) as long)) FROM Customer c") | |||||
| Long getLatestCodeNumber(); | |||||
| } | } | ||||
| @@ -3,4 +3,6 @@ package com.ffii.tsms.modules.data.entity; | |||||
| import com.ffii.core.support.AbstractRepository | import com.ffii.core.support.AbstractRepository | ||||
| interface LocationRepository : AbstractRepository<Location, Long> { | interface LocationRepository : AbstractRepository<Location, Long> { | ||||
| fun findByName(name: String): Location | |||||
| } | } | ||||
| @@ -25,4 +25,5 @@ public interface StaffRepository extends AbstractRepository<Staff, Long> { | |||||
| Optional<List<Staff>> findAllByDeletedFalse(); | Optional<List<Staff>> findAllByDeletedFalse(); | ||||
| Optional<Staff> findIdAndNameByUserIdAndDeletedFalse(Long id); | Optional<Staff> findIdAndNameByUserIdAndDeletedFalse(Long id); | ||||
| } | } | ||||
| @@ -10,6 +10,7 @@ import java.util.List; | |||||
| import static jakarta.persistence.CascadeType.ALL; | import static jakarta.persistence.CascadeType.ALL; | ||||
| @Entity | @Entity | ||||
| @Table(name = "subsidiary") | @Table(name = "subsidiary") | ||||
| public class Subsidiary extends BaseEntity<Long> { | public class Subsidiary extends BaseEntity<Long> { | ||||
| @@ -1,6 +1,7 @@ | |||||
| package com.ffii.tsms.modules.data.entity; | package com.ffii.tsms.modules.data.entity; | ||||
| import com.ffii.core.support.AbstractRepository; | import com.ffii.core.support.AbstractRepository; | ||||
| import org.springframework.data.jpa.repository.Query; | |||||
| import org.springframework.data.repository.query.Param; | import org.springframework.data.repository.query.Param; | ||||
| import java.util.List; | import java.util.List; | ||||
| @@ -12,4 +13,9 @@ public interface SubsidiaryRepository extends AbstractRepository<Subsidiary, Lon | |||||
| List<Subsidiary> findAllByDeletedFalseAndIdIn(List<Long> id); | List<Subsidiary> findAllByDeletedFalseAndIdIn(List<Long> id); | ||||
| Optional<Subsidiary> findByCode(@Param("code") String code); | 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(); | |||||
| } | } | ||||
| @@ -9,4 +9,6 @@ public interface TeamRepository extends AbstractRepository<Team, Long> { | |||||
| List<Team> findByDeletedFalse(); | List<Team> findByDeletedFalse(); | ||||
| Team findByStaff(Staff staff); | Team findByStaff(Staff staff); | ||||
| Team findByCode(String code); | |||||
| } | } | ||||
| @@ -3,4 +3,5 @@ package com.ffii.tsms.modules.data.entity; | |||||
| import com.ffii.core.support.AbstractRepository | import com.ffii.core.support.AbstractRepository | ||||
| interface WorkNatureRepository : AbstractRepository<WorkNature, Long> { | interface WorkNatureRepository : AbstractRepository<WorkNature, Long> { | ||||
| fun findByName(name: String): WorkNature? | |||||
| } | } | ||||
| @@ -37,6 +37,18 @@ open class CustomerService( | |||||
| return customerRepository.findByCode(code); | 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 { | open fun saveCustomer(saveCustomer: SaveCustomerRequest): SaveCustomerResponse { | ||||
| val duplicateCustomer = findCustomerByCode(saveCustomer.code) | val duplicateCustomer = findCustomerByCode(saveCustomer.code) | ||||
| @@ -68,19 +68,23 @@ open class DashboardService( | |||||
| fun searchCustomerSubsidiaryProject(args: Map<String, Any>): List<Map<String, Any>> { | fun searchCustomerSubsidiaryProject(args: Map<String, Any>): List<Map<String, Any>> { | ||||
| val sql = StringBuilder("select" | 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 id," | ||||
| + " p.id as projectId," | + " p.id as projectId," | ||||
| + " p.code as projectCode," | + " p.code as projectCode," | ||||
| + " p.name as projectName," | + " p.name as projectName," | ||||
| + " te.code as team," | + " te.code as team," | ||||
| + " s.name as teamLead," | + " s.name as teamLead," | ||||
| + " tg.name as expectedStage," | |||||
| + " GROUP_CONCAT(DISTINCT tg.name ORDER BY tg.name) as expectedStage," | |||||
| + " p.totalManhour as budgetedManhour," | + " p.totalManhour as budgetedManhour," | ||||
| + " sum(t.normalConsumed) + sum(t.otConsumed) as spentManhour," | + " sum(t.normalConsumed) + sum(t.otConsumed) as spentManhour," | ||||
| + " p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed) as remainedManhour," | + " 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 (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" | + " from project p" | ||||
| + " left join project_task pt on p.id = pt.project_id" | + " left join project_task pt on p.id = pt.project_id" | ||||
| + " left join timesheet t on pt.id = t.projectTaskId" | + " left join timesheet t on pt.id = t.projectTaskId" | ||||
| @@ -105,7 +109,7 @@ open class DashboardService( | |||||
| + " ) milestonePayment on milestonePayment.pid = p.id" | + " ) milestonePayment on milestonePayment.pid = p.id" | ||||
| + " where p.customerId = :customerId" | + " where p.customerId = :customerId" | ||||
| + " and p.customerSubsidiaryId = :subsidiaryId" | + " 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\")" | + " 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) | return jdbcDao.queryForList(sql.toString(), args) | ||||
| } | } | ||||
| fun searchCustomerNonSubsidiaryProject(args: Map<String, Any>): List<Map<String, Any>> { | fun searchCustomerNonSubsidiaryProject(args: Map<String, Any>): List<Map<String, Any>> { | ||||
| val sql = StringBuilder("select" | 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 id," | ||||
| + " p.id as projectId," | + " p.id as projectId," | ||||
| + " p.code as projectCode," | + " p.code as projectCode," | ||||
| + " p.name as projectName," | + " p.name as projectName," | ||||
| + " te.code as team," | + " te.code as team," | ||||
| + " s.name as teamLead," | + " s.name as teamLead," | ||||
| + " tg.name as expectedStage," | |||||
| + " GROUP_CONCAT(DISTINCT tg.name ORDER BY tg.name) as expectedStage," | |||||
| + " p.totalManhour as budgetedManhour," | + " p.totalManhour as budgetedManhour," | ||||
| + " COALESCE (sum(t.normalConsumed) + sum(t.otConsumed),0) as spentManhour," | + " COALESCE (sum(t.normalConsumed) + sum(t.otConsumed),0) as spentManhour," | ||||
| + " COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) as remainedManhour," | + " 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 (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" | + " from project p" | ||||
| + " left join project_task pt on p.id = pt.project_id" | + " left join project_task pt on p.id = pt.project_id" | ||||
| + " left join timesheet t on pt.id = t.projectTaskId" | + " left join timesheet t on pt.id = t.projectTaskId" | ||||
| @@ -159,10 +176,16 @@ open class DashboardService( | |||||
| + " where p.customerId = :customerId" | + " where p.customerId = :customerId" | ||||
| + " and isNull(p.customerSubsidiaryId)" | + " and isNull(p.customerSubsidiaryId)" | ||||
| + " and p.status not in (\"Pending to Start\",\"Completed\",\"Deleted\")" | + " 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) | return jdbcDao.queryForList(sql.toString(), args) | ||||
| } | } | ||||
| @@ -276,19 +299,23 @@ open class DashboardService( | |||||
| fun searchTeamProject(args: Map<String, Any>): List<Map<String, Any>> { | fun searchTeamProject(args: Map<String, Any>): List<Map<String, Any>> { | ||||
| val sql = StringBuilder( | val sql = StringBuilder( | ||||
| "select" | "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 id," | ||||
| + " p.id as projectId," | + " p.id as projectId," | ||||
| + " p.code as projectCode," | + " p.code as projectCode," | ||||
| + " p.name as projectName," | + " p.name as projectName," | ||||
| + " te.code as team," | + " te.code as team," | ||||
| + " s.name as teamLead," | + " s.name as teamLead," | ||||
| + " tg.name as expectedStage," | |||||
| + " GROUP_CONCAT(DISTINCT tg.name ORDER BY tg.name) as expectedStage," | |||||
| + " p.totalManhour as budgetedManhour," | + " p.totalManhour as budgetedManhour," | ||||
| + " COALESCE (sum(t.normalConsumed) + sum(t.otConsumed),0) as spentManhour," | + " COALESCE (sum(t.normalConsumed) + sum(t.otConsumed),0) as spentManhour," | ||||
| + " COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) as remainedManhour," | + " 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 (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" | + " from project p" | ||||
| + " left join project_task pt on p.id = pt.project_id" | + " left join project_task pt on p.id = pt.project_id" | ||||
| + " left join timesheet t on pt.id = t.projectTaskId" | + " left join timesheet t on pt.id = t.projectTaskId" | ||||
| @@ -312,12 +339,111 @@ open class DashboardService( | |||||
| + " ) milestonePayment on milestonePayment.pid = p.id" | + " ) milestonePayment on milestonePayment.pid = p.id" | ||||
| + " where p.teamLead = :teamLeadId" | + " where p.teamLead = :teamLeadId" | ||||
| + " and p.status not in (\"Pending to Start\",\"Completed\",\"Deleted\")" | + " 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) | 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>> { | fun searchFinancialSummaryCard(args: Map<String, Any>): List<Map<String, Any>> { | ||||
| val sql = StringBuilder( | val sql = StringBuilder( | ||||
| "select" | "select" | ||||
| @@ -327,13 +453,19 @@ open class DashboardService( | |||||
| + " coalesce(pj.totalFee,0) as totalFee," | + " coalesce(pj.totalFee,0) as totalFee," | ||||
| + " coalesce(pj.totalBudget,0) as totalBudget," | + " coalesce(pj.totalBudget,0) as totalBudget," | ||||
| + " coalesce(sum(i.issueAmount),0) as totalInvoiced," | + " 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," | + " coalesce(sum(i.paidAmount),0) as totalReceived," | ||||
| + " round(expenditure.cumulativeExpenditure,2) as cumulativeExpenditure," | + " round(expenditure.cumulativeExpenditure,2) as cumulativeExpenditure," | ||||
| + " case" | + " 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 'Positive'" | ||||
| + " when coalesce(round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2),0) < 1 then 'Negative'" | + " when coalesce(round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2),0) < 1 then 'Negative'" | ||||
| + " end as cashFlowStatus," | + " 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" | + " from team t" | ||||
| + " left join (" | + " left join (" | ||||
| + " select" | + " select" | ||||
| @@ -404,12 +536,18 @@ open class DashboardService( | |||||
| + " round(sum(p.expectedTotalFee) * 0.8,2) as totalBudget," | + " round(sum(p.expectedTotalFee) * 0.8,2) as totalBudget," | ||||
| + " round(expenditure.cumulativeExpenditure,2) as cumulativeExpenditure," | + " round(expenditure.cumulativeExpenditure,2) as cumulativeExpenditure," | ||||
| + " sum(i.issueAmount) as totalInvoiced," | + " sum(i.issueAmount) as totalInvoiced," | ||||
| + " sum(p.expectedTotalFee) - sum(i.issueAmount) as unInvoiced," | |||||
| + " sum(i.paidAmount) as totalReceived," | + " sum(i.paidAmount) as totalReceived," | ||||
| + " case" | + " case" | ||||
| + " when round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2) >= 1 then 'Positive'" | + " when round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2) >= 1 then 'Positive'" | ||||
| + " when round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2) < 1 then 'Negative'" | + " when round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2) < 1 then 'Negative'" | ||||
| + " end as cashFlowStatus," | + " 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" | + " from project p" | ||||
| + " left join (" | + " left join (" | ||||
| + " select" | + " select" | ||||
| @@ -445,16 +583,103 @@ open class DashboardService( | |||||
| return jdbcDao.queryForList(sql.toString(), args) | 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>> { | 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," | + " 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(round(expenditure.cumulativeExpenditure,2),0) as cumulativeExpenditure," | ||||
| + " coalesce(sum(i.issueAmount),0) as totalInvoiced," | + " coalesce(sum(i.issueAmount),0) as totalInvoiced," | ||||
| + " coalesce(sum(i.paidAmount),0) as totalReceived," | + " 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'" | + " when coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) < 1 then 'Negative'" | ||||
| + " end as cashFlowStatus," | + " end as cashFlowStatus," | ||||
| + " coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) as cpi," | + " 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" | + " 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" | + " end as totalUninvoiced" | ||||
| + " from team t" | + " from team t" | ||||
| + " left join project p on t.teamLead = p.teamLead" | |||||
| + " left join customer c on p.customerId = c.id" | |||||
| + " left join (" | + " left join (" | ||||
| + " select" | + " select" | ||||
| + " t3.id as tid," | + " t3.id as tid," | ||||
| + " c3.id as cid," | + " c3.id as cid," | ||||
| + " c3.code as customerCode," | |||||
| + " c3.name as customerName," | |||||
| + " sum(i3.issueAmount) as issueAmount," | + " sum(i3.issueAmount) as issueAmount," | ||||
| + " sum(i3.paidAmount) as paidAmount" | + " sum(i3.paidAmount) as paidAmount" | ||||
| + " from team t3" | + " from team t3" | ||||
| @@ -483,15 +713,34 @@ open class DashboardService( | |||||
| + " left join invoice i3 on p3.code = i3.projectCode" | + " left join invoice i3 on p3.code = i3.projectCode" | ||||
| + " where t3.deleted = 0" | + " where t3.deleted = 0" | ||||
| + " and p3.status = 'On-going'" | + " 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(" 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 (" | + " left join (" | ||||
| + " select" | + " select" | ||||
| + " r.teamId as teamId," | + " r.teamId as teamId," | ||||
| @@ -517,15 +766,15 @@ open class DashboardService( | |||||
| + " group by s2.hourlyRate,t1.id,c2.id" | + " group by s2.hourlyRate,t1.id,c2.id" | ||||
| + " ) as r" | + " ) as r" | ||||
| + " group by r.teamId, r.customerId" | + " 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" | + " 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(" 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) | 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'" | + " when coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) < 1 then 'Negative'" | ||||
| + " end as cashFlowStatus," | + " end as cashFlowStatus," | ||||
| + " coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) as cpi," | + " 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" | + " 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" | + " end as totalUninvoiced" | ||||
| + " from team t" | + " from team t" | ||||
| + " left join project p on t.teamLead = p.teamLead" | + " 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 (sum(i.issueAmount) - sum(i.paidAmount),0) as receivable," | ||||
| + " coalesce (round(sum(p.expectedTotalFee)*0.8,2),0) as totalBudget," | + " coalesce (round(sum(p.expectedTotalFee)*0.8,2),0) as totalBudget," | ||||
| + " coalesce (expenditure.expenditure,0) as totalExpenditure," | + " 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" | + " from project p" | ||||
| + " left join (" | + " left join (" | ||||
| + " select" | + " select" | ||||
| @@ -1083,7 +1344,9 @@ open class DashboardService( | |||||
| "select" | "select" | ||||
| + " concat(p.code,'-',p.name) as projectCodeAndName," | + " concat(p.code,'-',p.name) as projectCodeAndName," | ||||
| + " p.expectedTotalFee as totalFee," | + " 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" | + " case" | ||||
| + " when p.expectedTotalFee - expenditure.expenditure >= 0 then 'Within Budget'" | + " when p.expectedTotalFee - expenditure.expenditure >= 0 then 'Within Budget'" | ||||
| + " when p.expectedTotalFee - expenditure.expenditure < 0 then 'Overconsumption'" | + " when p.expectedTotalFee - expenditure.expenditure < 0 then 'Overconsumption'" | ||||
| @@ -1189,32 +1452,61 @@ open class DashboardService( | |||||
| + " left join staff s on s.id = ts.staffId" | + " left join staff s on s.id = ts.staffId" | ||||
| + " where p.id = :projectId" | + " where p.id = :projectId" | ||||
| + " group by p.id, t.id, t.name, g.name" | + " 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) | return jdbcDao.queryForList(sql.toString(), args) | ||||
| } | } | ||||
| fun monthlyActualTeamTotalManhoursSpent(args: Map<String, Any>): List<Map<String, Any>> { | fun monthlyActualTeamTotalManhoursSpent(args: Map<String, Any>): List<Map<String, Any>> { | ||||
| val sql = StringBuilder( | 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) | return jdbcDao.queryForList(sql.toString(), args) | ||||
| @@ -1253,25 +1545,54 @@ open class DashboardService( | |||||
| } | } | ||||
| fun weeklyActualTeamTotalManhoursSpent(args: Map<String, Any>): List<Map<String, Any>> { | fun weeklyActualTeamTotalManhoursSpent(args: Map<String, Any>): List<Map<String, Any>> { | ||||
| val sql = StringBuilder( | 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) | return jdbcDao.queryForList(sql.toString(), args) | ||||
| @@ -1346,6 +1667,7 @@ open class DashboardService( | |||||
| + " AND dates.missing_date = ts.recordDate" | + " AND dates.missing_date = ts.recordDate" | ||||
| + " WHERE" | + " WHERE" | ||||
| + " st.teamId = :teamId" | + " st.teamId = :teamId" | ||||
| + " and st.deleted = 0" | |||||
| + " AND ts.recordDate IS NULL" | + " AND ts.recordDate IS NULL" | ||||
| + " GROUP BY st.id, st.name" | + " GROUP BY st.id, st.name" | ||||
| + " ORDER BY" | + " ORDER BY" | ||||
| @@ -1595,6 +1917,7 @@ open class DashboardService( | |||||
| + " group by p.id, p.name" | + " group by p.id, p.name" | ||||
| + " ) as result on result.pid = p2.id" | + " ) as result on result.pid = p2.id" | ||||
| + " where s2.id = :staffId" | + " where s2.id = :staffId" | ||||
| + " and result.manhours > 0" | |||||
| + " group by p2.id, p2.name, result.manhours" | + " group by p2.id, p2.name, result.manhours" | ||||
| ) | ) | ||||
| @@ -1626,6 +1949,7 @@ open class DashboardService( | |||||
| + " group by p.id, p.name" | + " group by p.id, p.name" | ||||
| + " ) as result on result.pid = p2.id" | + " ) as result on result.pid = p2.id" | ||||
| + " where s2.id = :staffId" | + " where s2.id = :staffId" | ||||
| + " and result.manhours > 0" | |||||
| + " group by p2.id, p2.name, result.manhours" | + " group by p2.id, p2.name, result.manhours" | ||||
| ) | ) | ||||
| @@ -1656,6 +1980,7 @@ open class DashboardService( | |||||
| + " group by p.id, p.name" | + " group by p.id, p.name" | ||||
| + " ) as result on result.pid = p2.id" | + " ) as result on result.pid = p2.id" | ||||
| + " where s2.id = :staffId" | + " where s2.id = :staffId" | ||||
| + " and result.manhours > 0" | |||||
| + " group by p2.id, p2.name, result.manhours" | + " group by p2.id, p2.name, result.manhours" | ||||
| ) | ) | ||||
| @@ -38,6 +38,17 @@ open class SubsidiaryService( | |||||
| return subsidiaryRepository.findByCode(code); | 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 { | open fun saveSubsidiary(saveSubsidiary: SaveSubsidiaryRequest): SaveSubsidiaryResponse { | ||||
| val duplicateSubsidiary = findSubsidiaryByCode(saveSubsidiary.code) | val duplicateSubsidiary = findSubsidiaryByCode(saveSubsidiary.code) | ||||
| @@ -51,6 +51,7 @@ class DashboardController( | |||||
| fun searchCustomerSubsidiaryProject(request: HttpServletRequest?): List<Map<String, Any>> { | fun searchCustomerSubsidiaryProject(request: HttpServletRequest?): List<Map<String, Any>> { | ||||
| val customerId = request?.getParameter("customerId") | val customerId = request?.getParameter("customerId") | ||||
| val subsidiaryId = request?.getParameter("subsidiaryId") | val subsidiaryId = request?.getParameter("subsidiaryId") | ||||
| val tableSorting = request?.getParameter("tableSorting") | |||||
| val args = mutableMapOf<String, Any>() | val args = mutableMapOf<String, Any>() | ||||
| var result: List<Map<String, Any>> = emptyList() | var result: List<Map<String, Any>> = emptyList() | ||||
| if (customerId != null) { | if (customerId != null) { | ||||
| @@ -59,6 +60,9 @@ class DashboardController( | |||||
| if (subsidiaryId != null) { | if (subsidiaryId != null) { | ||||
| args["subsidiaryId"] = subsidiaryId | args["subsidiaryId"] = subsidiaryId | ||||
| } | } | ||||
| if (tableSorting != null) { | |||||
| args["tableSorting"] = tableSorting | |||||
| } | |||||
| if (customerId != null && subsidiaryId != null) { | if (customerId != null && subsidiaryId != null) { | ||||
| result = dashboardService.searchCustomerSubsidiaryProject(args) | result = dashboardService.searchCustomerSubsidiaryProject(args) | ||||
| @@ -80,16 +84,36 @@ class DashboardController( | |||||
| @GetMapping("/searchTeamProject") | @GetMapping("/searchTeamProject") | ||||
| fun searchTeamProject(request: HttpServletRequest?): List<Map<String, Any>> { | fun searchTeamProject(request: HttpServletRequest?): List<Map<String, Any>> { | ||||
| val teamLeadId = request?.getParameter("teamLeadId") | val teamLeadId = request?.getParameter("teamLeadId") | ||||
| val tableSorting = request?.getParameter("tableSorting") | |||||
| val args = mutableMapOf<String, Any>() | val args = mutableMapOf<String, Any>() | ||||
| var result: List<Map<String, Any>> = emptyList() | var result: List<Map<String, Any>> = emptyList() | ||||
| if (teamLeadId != null) { | if (teamLeadId != null) { | ||||
| args["teamLeadId"] = teamLeadId | args["teamLeadId"] = teamLeadId | ||||
| } | } | ||||
| if (tableSorting != null) { | |||||
| args["tableSorting"] = tableSorting | |||||
| } | |||||
| result = dashboardService.searchTeamProject(args) | result = dashboardService.searchTeamProject(args) | ||||
| return result | 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") | @GetMapping("/searchFinancialSummaryCard") | ||||
| fun searchFinancialSummaryCard(request: HttpServletRequest?): List<Map<String, Any>> { | fun searchFinancialSummaryCard(request: HttpServletRequest?): List<Map<String, Any>> { | ||||
| val authority = dashboardService.viewDashboardAuthority() | val authority = dashboardService.viewDashboardAuthority() | ||||
| @@ -19,7 +19,7 @@ import org.springframework.web.bind.annotation.* | |||||
| open class SkillController(private val skillService: SkillService) { | open class SkillController(private val skillService: SkillService) { | ||||
| @PostMapping("/save") | @PostMapping("/save") | ||||
| @PreAuthorize("hasAuthority('MAINTAIN_MASTERDATA')") | |||||
| // @PreAuthorize("hasAuthority('MAINTAIN_MASTERDATA')") | |||||
| open fun saveSkill(@Valid @RequestBody newSkill: NewSkillRequest): Skill { | open fun saveSkill(@Valid @RequestBody newSkill: NewSkillRequest): Skill { | ||||
| return skillService.saveOrUpdate(newSkill) | return skillService.saveOrUpdate(newSkill) | ||||
| } | } | ||||
| @@ -12,4 +12,6 @@ interface InvoiceRepository : AbstractRepository<Invoice, Long> { | |||||
| fun findInvoiceInfoByPaidAmountIsNotNull(): List<InvoiceInfo> | fun findInvoiceInfoByPaidAmountIsNotNull(): List<InvoiceInfo> | ||||
| fun findByInvoiceNo(invoiceNo: String): Invoice | fun findByInvoiceNo(invoiceNo: String): Invoice | ||||
| fun findAllByProjectCodeAndPaidAmountIsNotNull(projectCode: String): List<Invoice> | |||||
| } | } | ||||
| @@ -77,6 +77,9 @@ open class Project : BaseEntity<Long>() { | |||||
| @Column(name = "expectedTotalFee") | @Column(name = "expectedTotalFee") | ||||
| open var expectedTotalFee: Double? = null | open var expectedTotalFee: Double? = null | ||||
| @Column(name = "subContractFee") | |||||
| open var subContractFee: Double? = null | |||||
| @ManyToOne | @ManyToOne | ||||
| @JoinColumn(name = "serviceTypeId") | @JoinColumn(name = "serviceTypeId") | ||||
| open var serviceType: ServiceType? = null | open var serviceType: ServiceType? = null | ||||
| @@ -34,4 +34,6 @@ interface ProjectRepository : AbstractRepository<Project, Long> { | |||||
| fun findAllByStatusIsNotAndMainProjectIsNull(status: String): List<Project> | fun findAllByStatusIsNotAndMainProjectIsNull(status: String): List<Project> | ||||
| fun findAllByTeamLeadAndCustomer(teamLead: Staff, customer: Customer): List<Project> | fun findAllByTeamLeadAndCustomer(teamLead: Staff, customer: Customer): List<Project> | ||||
| fun findByCode(code: String): Project? | |||||
| } | } | ||||
| @@ -362,7 +362,7 @@ open class InvoiceService( | |||||
| // Check the import invoice with the data in DB | // Check the import invoice with the data in DB | ||||
| for (i in 2..sheet.lastRowNum){ | 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 | val sheetProjectCode = ExcelUtils.getCell(sheet, i, 1).stringCellValue | ||||
| checkInvoiceNo(sheetInvoice, invoices, invoicesResult, true) | checkInvoiceNo(sheetInvoice, invoices, invoicesResult, true) | ||||
| checkProjectCode(sheetProjectCode, projects, newProjectCodes) | 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 paymentMilestoneId = getMilestonePaymentId(ExcelUtils.getCell(sheet, i, 1).stringCellValue, ExcelUtils.getCell(sheet, i, 5).stringCellValue) | ||||
| // val milestonePayment = milestonePaymentRepository.findById(paymentMilestoneId).orElseThrow() | // val milestonePayment = milestonePaymentRepository.findById(paymentMilestoneId).orElseThrow() | ||||
| val invoice = Invoice().apply { | 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 | projectCode = ExcelUtils.getCell(sheet, i, 1).stringCellValue | ||||
| projectName = ExcelUtils.getCell(sheet, i, 2).stringCellValue | projectName = ExcelUtils.getCell(sheet, i, 2).stringCellValue | ||||
| team = ExcelUtils.getCell(sheet, i, 3).stringCellValue | team = ExcelUtils.getCell(sheet, i, 3).stringCellValue | ||||
| @@ -447,7 +447,7 @@ open class InvoiceService( | |||||
| val duplicateItemsInInvoice = checkDuplicateItemInImportedInvoice(sheet,2,0) | val duplicateItemsInInvoice = checkDuplicateItemInImportedInvoice(sheet,2,0) | ||||
| for (i in 2..sheet.lastRowNum){ | 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 | val sheetProjectCode = ExcelUtils.getCell(sheet, i, 1).stringCellValue | ||||
| checkInvoiceNo(sheetInvoice, invoices, invoicesResult, false) | checkInvoiceNo(sheetInvoice, invoices, invoicesResult, false) | ||||
| checkProjectCode(sheetProjectCode, projects, newProjectCodes) | checkProjectCode(sheetProjectCode, projects, newProjectCodes) | ||||
| @@ -489,7 +489,7 @@ open class InvoiceService( | |||||
| } | } | ||||
| for (i in 2..sheet.lastRowNum){ | 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.paidAmount = ExcelUtils.getCell(sheet, i, 5).numericCellValue.toBigDecimal() | ||||
| invoice.receiptDate = ExcelUtils.getCell(sheet, i, 4).dateCellValue.toInstant().atZone(ZoneId.systemDefault()).toLocalDate() | invoice.receiptDate = ExcelUtils.getCell(sheet, i, 4).dateCellValue.toInstant().atZone(ZoneId.systemDefault()).toLocalDate() | ||||
| saveAndFlush(invoice) | saveAndFlush(invoice) | ||||
| @@ -1,26 +1,26 @@ | |||||
| package com.ffii.tsms.modules.project.service | 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.common.SecurityUtils | ||||
| import com.ffii.tsms.modules.data.entity.* | 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.* | ||||
| import com.ffii.tsms.modules.project.entity.Milestone | 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.InvoiceInfoSearchInfo | ||||
| import com.ffii.tsms.modules.project.entity.projections.InvoiceSearchInfo | 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.project.web.models.* | ||||
| import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository | 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.stereotype.Service | ||||
| import org.springframework.transaction.annotation.Transactional | import org.springframework.transaction.annotation.Transactional | ||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
| import java.util.Optional | |||||
| import kotlin.jvm.optionals.getOrElse | |||||
| import kotlin.jvm.optionals.getOrNull | import kotlin.jvm.optionals.getOrNull | ||||
| @Service | @Service | ||||
| open class ProjectsService( | open class ProjectsService( | ||||
| private val projectRepository: ProjectRepository, | private val projectRepository: ProjectRepository, | ||||
| @@ -46,7 +46,11 @@ open class ProjectsService( | |||||
| private val timesheetRepository: TimesheetRepository, | private val timesheetRepository: TimesheetRepository, | ||||
| private val taskTemplateRepository: TaskTemplateRepository, | private val taskTemplateRepository: TaskTemplateRepository, | ||||
| private val subsidiaryContactService: SubsidiaryContactService, | 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> { | open fun allProjects(): List<ProjectSearchInfo> { | ||||
| return projectRepository.findProjectSearchInfoByOrderByCreatedDesc() | return projectRepository.findProjectSearchInfoByOrderByCreatedDesc() | ||||
| @@ -107,7 +111,8 @@ open class ProjectsService( | |||||
| code = project.code!!, | code = project.code!!, | ||||
| name = project.name!!, | name = project.name!!, | ||||
| status = project.status, | 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) | milestones = milestoneRepository.findAllByProject(project) | ||||
| .filter { milestone -> milestone.taskGroup?.id != null } | .filter { milestone -> milestone.taskGroup?.id != null } | ||||
| .associateBy { milestone -> milestone.taskGroup!!.id!! }.mapValues { (_, milestone) -> | .associateBy { milestone -> milestone.taskGroup!!.id!! }.mapValues { (_, milestone) -> | ||||
| @@ -164,10 +169,15 @@ open class ProjectsService( | |||||
| .orElseThrow() else null | .orElseThrow() else null | ||||
| val teamLead = staffRepository.findById(request.projectLeadId).orElseThrow() | val teamLead = staffRepository.findById(request.projectLeadId).orElseThrow() | ||||
| val customer = customerService.findCustomer(request.clientId) | 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 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 allTasksMap = tasksService.allTasks().associateBy { it.id } | ||||
| val taskGroupMap = tasksService.allTaskGroups().associateBy { it.id } | val taskGroupMap = tasksService.allTaskGroups().associateBy { it.id } | ||||
| @@ -179,15 +189,23 @@ open class ProjectsService( | |||||
| project.apply { | project.apply { | ||||
| name = request.projectName | name = request.projectName | ||||
| description = request.projectDescription | 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 | expectedTotalFee = request.expectedProjectFee | ||||
| subContractFee = request.subContractFee | |||||
| totalManhour = request.totalManhour | totalManhour = request.totalManhour | ||||
| actualStart = request.projectActualStart | actualStart = request.projectActualStart | ||||
| actualEnd = request.projectActualEnd | actualEnd = request.projectActualEnd | ||||
| status = if (this.status == "Deleted" || this.deleted == true) "Deleted" | status = if (this.status == "Deleted" || this.deleted == true) "Deleted" | ||||
| else if (this.actualStart != null && this.actualEnd != null) "Completed" | else if (this.actualStart != null && this.actualEnd != null) "Completed" | ||||
| else if (this.actualStart != null) "On-going" | else if (this.actualStart != null) "On-going" | ||||
| else "Pending To Start" | |||||
| else request.projectStatus ?: "Pending To Start" | |||||
| isClpProject = request.isClpProject | isClpProject = request.isClpProject | ||||
| this.mainProject = mainProject | this.mainProject = mainProject | ||||
| @@ -203,11 +221,11 @@ open class ProjectsService( | |||||
| this.teamLead = teamLead | this.teamLead = teamLead | ||||
| this.customer = customer | this.customer = customer | ||||
| custLeadName = | custLeadName = | ||||
| if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact.name else subsidiaryContact.name | |||||
| if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact?.name else subsidiaryContact?.name | |||||
| custLeadEmail = | custLeadEmail = | ||||
| if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact.email else subsidiaryContact.email | |||||
| if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact?.email else subsidiaryContact?.email | |||||
| custLeadPhone = | 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.customerSubsidiary = customerSubsidiary | ||||
| this.customerContact = if (customerSubsidiary == null) clientContact else null | this.customerContact = if (customerSubsidiary == null) clientContact else null | ||||
| this.subsidiaryContact = if (customerSubsidiary != null) subsidiaryContact else null | this.subsidiaryContact = if (customerSubsidiary != null) subsidiaryContact else null | ||||
| @@ -279,14 +297,15 @@ open class ProjectsService( | |||||
| if (milestones.isNotEmpty()) { | if (milestones.isNotEmpty()) { | ||||
| project.apply { | project.apply { | ||||
| planStart = milestones.mapNotNull { it.startDate }.minOrNull() | 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 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 = | val gradeAllocationsToDelete = | ||||
| gradeAllocationRepository.findByProject(project).subtract(gradeAllocations.toSet()) | gradeAllocationRepository.findByProject(project).subtract(gradeAllocations.toSet()) | ||||
| milestoneRepository.deleteAll(milestonesToDelete) | 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 -> | val subsidiaryContact = project.customerSubsidiary?.id?.let { subsidiaryId -> | ||||
| subsidiaryContactService.findAllBySubsidiaryId(subsidiaryId) | subsidiaryContactService.findAllBySubsidiaryId(subsidiaryId) | ||||
| } ?: emptyList() | } ?: emptyList() | ||||
| val customerContact = project.customer?.id?.let { customerId -> customerContactService.findAllByCustomerId(customerId) } | |||||
| ?: emptyList() | |||||
| val customerContact = | |||||
| project.customer?.id?.let { customerId -> customerContactService.findAllByCustomerId(customerId) } | |||||
| ?: emptyList() | |||||
| MainProjectDetails( | MainProjectDetails( | ||||
| projectId = project.id, | projectId = project.id, | ||||
| @@ -441,10 +462,308 @@ open class ProjectsService( | |||||
| buildingTypeIds = project.buildingTypes.mapNotNull { buildingType -> buildingType.id }, | buildingTypeIds = project.buildingTypes.mapNotNull { buildingType -> buildingType.id }, | ||||
| workNatureIds = project.workNatures.mapNotNull { workNature -> workNature.id }, | workNatureIds = project.workNatures.mapNotNull { workNature -> workNature.id }, | ||||
| clientId = project.customer?.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, | 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" | |||||
| } | |||||
| } | } | ||||
| @@ -2,15 +2,21 @@ package com.ffii.tsms.modules.project.web | |||||
| import com.ffii.core.exception.NotFoundException | import com.ffii.core.exception.NotFoundException | ||||
| import com.ffii.tsms.modules.data.entity.* | 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.ProjectCategory | ||||
| import com.ffii.tsms.modules.project.entity.ProjectRepository | 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.service.ProjectsService | ||||
| import com.ffii.tsms.modules.project.web.models.* | import com.ffii.tsms.modules.project.web.models.* | ||||
| import jakarta.servlet.http.HttpServletRequest | |||||
| import jakarta.validation.Valid | 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.HttpStatus | ||||
| import org.springframework.http.ResponseEntity | |||||
| import org.springframework.web.bind.ServletRequestBindingException | |||||
| import org.springframework.web.bind.annotation.* | import org.springframework.web.bind.annotation.* | ||||
| import org.springframework.web.multipart.MultipartHttpServletRequest | |||||
| @RestController | @RestController | ||||
| @RequestMapping("/projects") | @RequestMapping("/projects") | ||||
| @@ -85,4 +91,20 @@ class ProjectsController(private val projectsService: ProjectsService, private v | |||||
| fun projectWorkNatures(): List<WorkNature> { | fun projectWorkNatures(): List<WorkNature> { | ||||
| return projectsService.allWorkNatures() | 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)) | |||||
| } | |||||
| } | } | ||||
| @@ -40,5 +40,6 @@ data class EditProjectDetails( | |||||
| val milestones: Map<Long, Milestone>, | val milestones: Map<Long, Milestone>, | ||||
| // Miscellaneous | // Miscellaneous | ||||
| val expectedProjectFee: Double? | |||||
| val expectedProjectFee: Double?, | |||||
| val subContractFee: Double? | |||||
| ) | ) | ||||
| @@ -27,4 +27,5 @@ data class MainProjectDetails ( | |||||
| val clientSubsidiaryId: Long?, | val clientSubsidiaryId: Long?, | ||||
| val expectedProjectFee: Double?, | val expectedProjectFee: Double?, | ||||
| val subContractFee: Double?, | |||||
| ) | ) | ||||
| @@ -7,7 +7,7 @@ import java.time.LocalDate | |||||
| data class NewProjectRequest( | data class NewProjectRequest( | ||||
| // Project details | // Project details | ||||
| // @field:NotBlank(message = "project code cannot be empty") | // @field:NotBlank(message = "project code cannot be empty") | ||||
| // val projectCode: String, | |||||
| val projectCode: String?, | |||||
| @field:NotBlank(message = "project name cannot be empty") | @field:NotBlank(message = "project name cannot be empty") | ||||
| val projectName: String, | val projectName: String, | ||||
| val projectCategoryId: Long, | val projectCategoryId: Long, | ||||
| @@ -16,8 +16,10 @@ data class NewProjectRequest( | |||||
| val projectId: Long?, | val projectId: Long?, | ||||
| val projectActualStart: LocalDate?, | val projectActualStart: LocalDate?, | ||||
| val projectActualEnd: LocalDate?, | val projectActualEnd: LocalDate?, | ||||
| val projectPlanEnd: LocalDate?, | |||||
| val isClpProject: Boolean?, | val isClpProject: Boolean?, | ||||
| val mainProjectId: Long?, | val mainProjectId: Long?, | ||||
| val projectStatus: String?, | |||||
| val serviceTypeId: Long, | val serviceTypeId: Long, | ||||
| val fundingTypeId: Long, | val fundingTypeId: Long, | ||||
| @@ -30,7 +32,7 @@ data class NewProjectRequest( | |||||
| // Client details | // Client details | ||||
| val clientId: Long, | val clientId: Long, | ||||
| val clientContactId: Long, | |||||
| val clientContactId: Long?, | |||||
| val clientSubsidiaryId: Long?, | val clientSubsidiaryId: Long?, | ||||
| // Allocation | // Allocation | ||||
| @@ -43,7 +45,8 @@ data class NewProjectRequest( | |||||
| val milestones: Map<Long, Milestone>, | val milestones: Map<Long, Milestone>, | ||||
| // Miscellaneous | // Miscellaneous | ||||
| val expectedProjectFee: Double | |||||
| val expectedProjectFee: Double, | |||||
| val subContractFee: Double? | |||||
| ) | ) | ||||
| data class TaskGroupAllocation( | data class TaskGroupAllocation( | ||||
| @@ -2,6 +2,7 @@ package com.ffii.tsms.modules.report.service | |||||
| import com.ffii.core.support.JdbcDao | import com.ffii.core.support.JdbcDao | ||||
| import com.ffii.tsms.modules.data.entity.Customer | 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.Salary | ||||
| import com.ffii.tsms.modules.data.entity.Staff | import com.ffii.tsms.modules.data.entity.Staff | ||||
| import com.ffii.tsms.modules.data.entity.Team | 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.ss.util.CellUtil | ||||
| import org.apache.poi.xssf.usermodel.XSSFWorkbook | import org.apache.poi.xssf.usermodel.XSSFWorkbook | ||||
| import org.apache.poi.ss.usermodel.FormulaEvaluator | 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.core.io.ClassPathResource | ||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
| import java.io.ByteArrayOutputStream | import java.io.ByteArrayOutputStream | ||||
| @@ -54,6 +57,7 @@ open class ReportService( | |||||
| private val COMPLETE_PROJECT_OUTSTANDING_RECEIVABLE = | private val COMPLETE_PROJECT_OUTSTANDING_RECEIVABLE = | ||||
| "templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx" | "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 COMPLETION_PROJECT = "templates/report/AR05_Project Completion Report.xlsx" | ||||
| private val CROSS_TEAM_CHARGE_REPORT = "templates/report/Cross Team Charge Report.xlsx" | |||||
| // ==============================|| GENERATE REPORT ||============================== // | // ==============================|| GENERATE REPORT ||============================== // | ||||
| fun generalCreateReportIndexed( // just loop through query records one by one, return rowIndex | fun generalCreateReportIndexed( // just loop through query records one by one, return rowIndex | ||||
| @@ -65,7 +69,7 @@ open class ReportService( | |||||
| var rowIndex = startRow | var rowIndex = startRow | ||||
| var columnIndex = startColumn | var columnIndex = startColumn | ||||
| result.forEachIndexed { index, obj -> | 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()) | tempCell.setCellValue((index + 1).toDouble()) | ||||
| val keys = obj.keys.toList() | val keys = obj.keys.toList() | ||||
| keys.forEachIndexed { keyIndex, key -> | keys.forEachIndexed { keyIndex, key -> | ||||
| @@ -299,6 +303,25 @@ open class ReportService( | |||||
| return outputStream.toByteArray() | 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 ||============================== // | // ==============================|| CREATE REPORT ||============================== // | ||||
| // EX01 Financial Report | // EX01 Financial Report | ||||
| @@ -435,7 +458,8 @@ open class ReportService( | |||||
| val uninvoiceCell = row.createCell(12) | val uninvoiceCell = row.createCell(12) | ||||
| uninvoiceCell.apply { | uninvoiceCell.apply { | ||||
| cellFormula = | 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 | cellStyle.dataFormat = accountingStyle | ||||
| } | } | ||||
| @@ -528,6 +552,11 @@ open class ReportService( | |||||
| CellUtil.setCellStyleProperty(sumUInvoiceCell, "borderBottom", BorderStyle.DOUBLE) | CellUtil.setCellStyleProperty(sumUInvoiceCell, "borderBottom", BorderStyle.DOUBLE) | ||||
| val lastCpiCell = row.createCell(13) | 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, "borderTop", BorderStyle.THIN) | ||||
| CellUtil.setCellStyleProperty(lastCpiCell, "borderBottom", BorderStyle.DOUBLE) | CellUtil.setCellStyleProperty(lastCpiCell, "borderBottom", BorderStyle.DOUBLE) | ||||
| @@ -606,7 +635,7 @@ open class ReportService( | |||||
| val row9: Row = sheet.getRow(rowNum) | val row9: Row = sheet.getRow(rowNum) | ||||
| val cell5 = row9.createCell(2) | val cell5 = row9.createCell(2) | ||||
| cell5.apply { | cell5.apply { | ||||
| cellFormula = "N${lastRowNum}" | |||||
| cellFormula = "M${lastRowNum}" | |||||
| cellStyle.dataFormat = accountingStyle | cellStyle.dataFormat = accountingStyle | ||||
| } | } | ||||
| @@ -691,12 +720,12 @@ open class ReportService( | |||||
| rowIndex = 10 | rowIndex = 10 | ||||
| sheet.getRow(rowIndex).apply { | sheet.getRow(rowIndex).apply { | ||||
| createCell(1).apply { | createCell(1).apply { | ||||
| setCellValue(project.expectedTotalFee!! * 0.8) | |||||
| setCellValue(if (project.expectedTotalFee != null) project.expectedTotalFee!! * 0.8 else 0.0) | |||||
| cellStyle.dataFormat = accountingStyle | cellStyle.dataFormat = accountingStyle | ||||
| } | } | ||||
| createCell(2).apply { | createCell(2).apply { | ||||
| setCellValue(project.expectedTotalFee!!) | |||||
| setCellValue(project.expectedTotalFee ?: 0.0) | |||||
| cellStyle.dataFormat = accountingStyle | cellStyle.dataFormat = accountingStyle | ||||
| } | } | ||||
| } | } | ||||
| @@ -979,10 +1008,15 @@ open class ReportService( | |||||
| project.milestones.forEach { milestone: Milestone -> | project.milestones.forEach { milestone: Milestone -> | ||||
| val manHoursSpent = groupedTimesheets[Pair(project.id, milestone.id)]?.sum() ?: 0.0 | 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(project.name + " : " + milestone.taskGroup?.name + " : " + ChronoUnit.DAYS.between(LocalDate.now(), milestone.endDate)) | ||||
| // logger.info(daysUntilCurrentStageEnd) | // 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++ | milestoneCount++ | ||||
| val tempRow = sheet.getRow(rowIndex) ?: sheet.createRow(rowIndex) | val tempRow = sheet.getRow(rowIndex) ?: sheet.createRow(rowIndex) | ||||
| rowIndex++ | rowIndex++ | ||||
| @@ -1004,7 +1038,7 @@ open class ReportService( | |||||
| // | // | ||||
| // val resourceUtilization = | // val resourceUtilization = | ||||
| // manHoursSpent / (milestone.stagePercentAllocation!! / 100 * project.totalManhour!!) | // manHoursSpent / (milestone.stagePercentAllocation!! / 100 * project.totalManhour!!) | ||||
| setCellValue(resourceUtilization) | |||||
| setCellValue(resourceUtilization) | |||||
| // } else { | // } else { | ||||
| // setCellValue(0.0) | // setCellValue(0.0) | ||||
| // } | // } | ||||
| @@ -1080,6 +1114,13 @@ open class ReportService( | |||||
| val boldFont = workbook.createFont() | val boldFont = workbook.createFont() | ||||
| boldFont.bold = true | boldFont.bold = true | ||||
| boldStyle.setFont(boldFont) | 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 daysOfMonth = (1..month.lengthOfMonth()).map { day -> | ||||
| val date = month.withDayOfMonth(day) | val date = month.withDayOfMonth(day) | ||||
| val formattedDate = date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")) | val formattedDate = date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")) | ||||
| @@ -1228,7 +1269,7 @@ open class ReportService( | |||||
| projectList.forEachIndexed { index, title -> | projectList.forEachIndexed { index, title -> | ||||
| tempCell = sheet.getRow(7).createCell(columnIndex + index) | tempCell = sheet.getRow(7).createCell(columnIndex + index) | ||||
| tempCell.setCellValue(title) | tempCell.setCellValue(title) | ||||
| tempCell.cellStyle = boldStyle | |||||
| tempCell.cellStyle = projectsStyle | |||||
| CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER) | CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER) | ||||
| CellUtil.setVerticalAlignment(tempCell, VerticalAlignment.CENTER) | CellUtil.setVerticalAlignment(tempCell, VerticalAlignment.CENTER) | ||||
| CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom) | CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom) | ||||
| @@ -1271,7 +1312,7 @@ open class ReportService( | |||||
| ///////////////////////////////////////////////////////// Leave Hours title //////////////////////////////////////////////////////////////////// | ///////////////////////////////////////////////////////// Leave Hours title //////////////////////////////////////////////////////////////////// | ||||
| tempCell = sheet.getRow(rowIndex).createCell(columnIndex) | tempCell = sheet.getRow(rowIndex).createCell(columnIndex) | ||||
| tempCell.setCellValue("Leave Hours") | tempCell.setCellValue("Leave Hours") | ||||
| tempCell.cellStyle = boldStyle | |||||
| tempCell.cellStyle = projectsStyle | |||||
| CellUtil.setVerticalAlignment(tempCell, VerticalAlignment.CENTER) | CellUtil.setVerticalAlignment(tempCell, VerticalAlignment.CENTER) | ||||
| CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER) | CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER) | ||||
| CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom) | CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom) | ||||
| @@ -1279,8 +1320,10 @@ open class ReportService( | |||||
| columnIndex += 1 | columnIndex += 1 | ||||
| tempCell = sheet.getRow(rowIndex).createCell(columnIndex) | tempCell = sheet.getRow(rowIndex).createCell(columnIndex) | ||||
| tempCell.setCellValue("Daily Manhour Spent\n(Excluding Leave Hours)") | 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) | CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom) | ||||
| sheet.addMergedRegion(CellRangeAddress(6, 6, 2, columnIndex)) | sheet.addMergedRegion(CellRangeAddress(6, 6, 2, columnIndex)) | ||||
| @@ -1391,7 +1434,7 @@ open class ReportService( | |||||
| rowIndex = 5 | rowIndex = 5 | ||||
| columnIndex = 0 | columnIndex = 0 | ||||
| generalCreateReportIndexed(sheet, result, rowIndex, columnIndex) | |||||
| generalCreateReportIndexed(sheet, result.distinct(), rowIndex, columnIndex) | |||||
| return workbook | return workbook | ||||
| } | } | ||||
| @@ -1481,7 +1524,7 @@ open class ReportService( | |||||
| setDataAndConditionalFormatting(workbook, sheet, lateStartData, evaluator) | setDataAndConditionalFormatting(workbook, sheet, lateStartData, evaluator) | ||||
| // Automatically adjust column widths to fit content | // Automatically adjust column widths to fit content | ||||
| autoSizeColumns(sheet) | |||||
| autoSizeColumns(sheet) | |||||
| return workbook | return workbook | ||||
| } | } | ||||
| @@ -1627,7 +1670,8 @@ open class ReportService( | |||||
| // NEW Column F: Subsidiary Name | // NEW Column F: Subsidiary Name | ||||
| val subsidiaryNameCell = row.createCell(5) | val subsidiaryNameCell = row.createCell(5) | ||||
| // subsidiaryNameCell.setCellValue(data["subsidiary_name"] as String) | // 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) | subsidiaryNameCell.setCellValue(subsidiaryName) | ||||
| // Column G: Project Plan Start Date | // Column G: Project Plan Start Date | ||||
| @@ -1748,136 +1792,139 @@ open class ReportService( | |||||
| ) | ) | ||||
| return jdbcDao.queryForList(sql.toString(), args) | return jdbcDao.queryForList(sql.toString(), args) | ||||
| } | } | ||||
| open fun getProjectCompletionReport(args: Map<String, Any>): List<Map<String, Any>> { | 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) { | if (args.get("outstanding") as Boolean) { | ||||
| sql.append(" result.projectFee - COALESCE(i.paidAmount, 0) as `Receivable Remained`, ") | 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(" result.projectFee - (result.totalBudget - COALESCE(i.issueAmount , 0) + COALESCE(i.issueAmount, 0) - COALESCE(i.paidAmount, 0)) as `Receivable Remained`, ") | ||||
| } | } | ||||
| sql.append( | sql.append( | ||||
| " DATE_FORMAT(result.actualEnd, '%d/%m/%Y') as actualEnd " | " 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.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) | return jdbcDao.queryForList(sql.toString(), args) | ||||
| } | } | ||||
| open fun getProjectResourceOverconsumptionReport(args: Map<String, Any>): List<Map<String, Any>> { | 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) { | if (args != null) { | ||||
| var statusFilter: String = "" | var statusFilter: String = "" | ||||
| if (args.containsKey("teamId")) | if (args.containsKey("teamId")) | ||||
| sql.append("and t.id = :teamId") | |||||
| sql.append(" and t.id = :teamId") | |||||
| if (args.containsKey("custId")) | if (args.containsKey("custId")) | ||||
| sql.append("and c.id = :custId") | |||||
| sql.append(" and c.id = :custId") | |||||
| if (args.containsKey("subsidiaryId")) | if (args.containsKey("subsidiaryId")) | ||||
| sql.append("and ss.id = :subsidiaryId") | |||||
| sql.append(" and ss.id = :subsidiaryId") | |||||
| if (args.containsKey("status")) | if (args.containsKey("status")) | ||||
| statusFilter = when (args.get("status")) { | statusFilter = when (args.get("status")) { | ||||
| "Potential Overconsumption" -> "and " + | |||||
| "Potential Overconsumption" -> " and " + | |||||
| " (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit " + | " (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit " + | ||||
| " and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " + | " and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " + | ||||
| " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit " + | " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit " + | ||||
| " and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1 " | " and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1 " | ||||
| "All" -> "and " + | |||||
| "All" -> " and " + | |||||
| " (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit " + | " (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit " + | ||||
| " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit " | " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit " | ||||
| // "Overconsumption" -> "and " + | |||||
| // "Overconsumption" -> " and " + | |||||
| // " ((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " + | // " ((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " + | ||||
| // " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 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 salary s2 on s.salaryId = s2.salaryPoint" | ||||
| + " left join team t2 on t2.id = s.teamId" | + " 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 (" | + " cte_invoice as (" | ||||
| + " select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount" | + " select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount" | ||||
| + " from invoice i" | + " 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," | + " 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.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.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" | + " s.name, s.staffId, g.code as gradeCode, g.name as gradeName, t2.code as teamCode, t2.name as teamName" | ||||
| + " from project p" | + " from project p" | ||||
| + " left join cte_timesheet cte_ts on p.code = cte_ts.code" | + " 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 customer c on c.id = p.customerId" | ||||
| + " left join tsmsdb.team t on t.teamLead = p.teamLead" | + " left join tsmsdb.team t on t.teamLead = p.teamLead" | ||||
| + " left join cte_invoice cte_i on cte_i.code = p.code" | + " 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) { | if (info["code"] == item["code"] && "subsidiary" !in info) { | ||||
| info["subsidiary"] = item.getValue("subsidiary") | info["subsidiary"] = item.getValue("subsidiary") | ||||
| } | } | ||||
| if (info["manhourExpenditure"] != item.getValue("sumManhourExpenditure")) { | |||||
| info["manhourExpenditure"] = item.getValue("sumManhourExpenditure") | |||||
| } | |||||
| if (info["description"] != item.getValue("description")) { | if (info["description"] != item.getValue("description")) { | ||||
| info["description"] = item.getValue("description") | info["description"] = item.getValue("description") | ||||
| } | } | ||||
| @@ -2162,9 +2222,9 @@ open class ReportService( | |||||
| rowNum = 6 | rowNum = 6 | ||||
| val row6: Row = sheet.getRow(rowNum) | val row6: Row = sheet.getRow(rowNum) | ||||
| val row6Cell = row6.getCell(1) | 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() | info.getValue("subsidiary").toString() | ||||
| }else{ | |||||
| } else { | |||||
| info.getValue("client").toString() | info.getValue("client").toString() | ||||
| } | } | ||||
| row6Cell.setCellValue(clientSubsidiary) | row6Cell.setCellValue(clientSubsidiary) | ||||
| @@ -2335,7 +2395,8 @@ open class ReportService( | |||||
| } | } | ||||
| val totalManhourECell = totalManhourERow.getCell(1) ?: totalManhourERow.createCell(1) | val totalManhourECell = totalManhourERow.getCell(1) ?: totalManhourERow.createCell(1) | ||||
| totalManhourECell.apply { | 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 | cellStyle.dataFormat = accountingStyle | ||||
| } | } | ||||
| CellUtil.setCellStyleProperty(totalManhourETitleCell, "borderBottom", BorderStyle.THIN) | CellUtil.setCellStyleProperty(totalManhourETitleCell, "borderBottom", BorderStyle.THIN) | ||||
| @@ -2358,10 +2419,10 @@ open class ReportService( | |||||
| return workbook | 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( | val sql = StringBuilder( | ||||
| " with cte_timesheet as ( " | " 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" | + " t.recordDate" | ||||
| + " from timesheet t" | + " from timesheet t" | ||||
| + " left join project_task pt on pt.id = t.projectTaskId" | + " 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" | + " left join team t2 on t2.id = s.teamId" | ||||
| + " where ISNULL(p.code) = False" | + " where ISNULL(p.code) = False" | ||||
| ) | ) | ||||
| if(clientId != null){ | |||||
| if(type == "client"){ | |||||
| if (clientId != null) { | |||||
| if (type == "client") { | |||||
| sql.append( | sql.append( | ||||
| " and c.id = :clientId " | " and c.id = :clientId " | ||||
| ) | ) | ||||
| } | } | ||||
| if(type == "subsidiary"){ | |||||
| if (type == "subsidiary") { | |||||
| sql.append( | sql.append( | ||||
| " and s2.id = :clientId " | " and s2.id = :clientId " | ||||
| ) | ) | ||||
| } | } | ||||
| } | } | ||||
| if(teamId != null){ | |||||
| if (teamId != null) { | |||||
| sql.append( | sql.append( | ||||
| " and p.teamLead = :teamId " | " and p.teamLead = :teamId " | ||||
| ) | ) | ||||
| @@ -2417,9 +2478,9 @@ open class ReportService( | |||||
| val queryList = jdbcDao.queryForList(sql.toString(), args) | val queryList = jdbcDao.queryForList(sql.toString(), args) | ||||
| val costAndExpenseList = mutableListOf<Map<String, Any?>>() | val costAndExpenseList = mutableListOf<Map<String, Any?>>() | ||||
| for(item in queryList){ | |||||
| for (item in queryList) { | |||||
| val hourlyRate = (item.getValue("hourlyRate") as BigDecimal).toDouble() | val hourlyRate = (item.getValue("hourlyRate") as BigDecimal).toDouble() | ||||
| if(item["code"] !in costAndExpenseList){ | |||||
| if (item["code"] !in costAndExpenseList.map { it["code"] }) { | |||||
| costAndExpenseList.add( | costAndExpenseList.add( | ||||
| mapOf( | mapOf( | ||||
| "code" to item["code"], | "code" to item["code"], | ||||
| @@ -2428,22 +2489,27 @@ open class ReportService( | |||||
| "subsidiary" to item["subsidiary"], | "subsidiary" to item["subsidiary"], | ||||
| "teamLead" to item["teamLead"], | "teamLead" to item["teamLead"], | ||||
| "budget" to item["expectedTotalFee"], | "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"]) }!! | val existingMap = costAndExpenseList.find { it.containsValue(item["code"]) }!! | ||||
| costAndExpenseList[costAndExpenseList.indexOf(existingMap)] = existingMap.toMutableMap().apply { | 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 budget = (item["budget"] as? Double)?.times(0.8) ?: 0.0 | ||||
| val budgetRemain = budget - (item["manhourExpenditure"] as? Double ?: 0.0) | val budgetRemain = budget - (item["manhourExpenditure"] as? Double ?: 0.0) | ||||
| val remainingPercent = (budgetRemain / budget) | val remainingPercent = (budgetRemain / budget) | ||||
| @@ -2462,7 +2528,7 @@ open class ReportService( | |||||
| clientId: Long?, | clientId: Long?, | ||||
| budgetPercentage: Double?, | budgetPercentage: Double?, | ||||
| type: String | type: String | ||||
| ): Workbook{ | |||||
| ): Workbook { | |||||
| val resource = ClassPathResource(templatePath) | val resource = ClassPathResource(templatePath) | ||||
| val templateInputStream = resource.inputStream | val templateInputStream = resource.inputStream | ||||
| val workbook: Workbook = XSSFWorkbook(templateInputStream) | val workbook: Workbook = XSSFWorkbook(templateInputStream) | ||||
| @@ -2480,9 +2546,9 @@ open class ReportService( | |||||
| rowNum = 2 | rowNum = 2 | ||||
| val row2: Row = sheet.getRow(rowNum) | val row2: Row = sheet.getRow(rowNum) | ||||
| val row2Cell = row2.getCell(2) | val row2Cell = row2.getCell(2) | ||||
| if(teamId == null){ | |||||
| if (teamId == null) { | |||||
| row2Cell.setCellValue("All") | row2Cell.setCellValue("All") | ||||
| }else{ | |||||
| } else { | |||||
| val sql = StringBuilder( | val sql = StringBuilder( | ||||
| " select t.id, t.code, t.name, concat(t.code, \" - \" ,t.name) as teamLead from team t where t.id = :teamId " | " 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 | rowNum = 3 | ||||
| val row3: Row = sheet.getRow(rowNum) | val row3: Row = sheet.getRow(rowNum) | ||||
| val row3Cell = row3.getCell(2) | val row3Cell = row3.getCell(2) | ||||
| if(clientId == null){ | |||||
| if (clientId == null) { | |||||
| row3Cell.setCellValue("All") | row3Cell.setCellValue("All") | ||||
| }else{ | |||||
| val sql= StringBuilder() | |||||
| if(type == "client"){ | |||||
| } else { | |||||
| val sql = StringBuilder() | |||||
| if (type == "client") { | |||||
| sql.append( | sql.append( | ||||
| " select c.id, c.name from customer c where c.id = :clientId " | " select c.id, c.name from customer c where c.id = :clientId " | ||||
| ) | ) | ||||
| } | } | ||||
| if(type == "subsidiary"){ | |||||
| if (type == "subsidiary") { | |||||
| sql.append( | sql.append( | ||||
| " select s.id, s.name from subsidiary s where s.id = :clientId " | " 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?>> | val filterList: List<Map<String, Any?>> | ||||
| if(budgetPercentage != null){ | |||||
| if (budgetPercentage != null) { | |||||
| filterList = costAndExpenseList.filter { ((it["budgetPercentage"] as? Double) ?: 0.0) <= budgetPercentage } | filterList = costAndExpenseList.filter { ((it["budgetPercentage"] as? Double) ?: 0.0) <= budgetPercentage } | ||||
| }else{ | |||||
| } else { | |||||
| filterList = costAndExpenseList | filterList = costAndExpenseList | ||||
| } | } | ||||
| rowNum = 6 | rowNum = 6 | ||||
| for(item in filterList){ | |||||
| for (item in filterList) { | |||||
| val index = filterList.indexOf(item) | val index = filterList.indexOf(item) | ||||
| val row: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) | val row: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) | ||||
| val cell = row.getCell(0) ?: row.createCell(0) | val cell = row.getCell(0) ?: row.createCell(0) | ||||
| @@ -2571,13 +2637,13 @@ open class ReportService( | |||||
| val cell8 = row.getCell(8) ?: row.createCell(8) | val cell8 = row.getCell(8) ?: row.createCell(8) | ||||
| cell8.apply { | cell8.apply { | ||||
| cellFormula = "G${rowNum+1}-H${rowNum+1}" | |||||
| cellFormula = "G${rowNum + 1}-H${rowNum + 1}" | |||||
| } | } | ||||
| CellUtil.setCellStyleProperty(cell8, "dataFormat", accountingStyle) | CellUtil.setCellStyleProperty(cell8, "dataFormat", accountingStyle) | ||||
| val cell9 = row.getCell(9) ?: row.createCell(9) | val cell9 = row.getCell(9) ?: row.createCell(9) | ||||
| cell9.apply { | cell9.apply { | ||||
| cellFormula = "I${rowNum+1}/G${rowNum+1}" | |||||
| cellFormula = "I${rowNum + 1}/G${rowNum + 1}" | |||||
| } | } | ||||
| CellUtil.setCellStyleProperty(cell9, "dataFormat", percentStyle) | CellUtil.setCellStyleProperty(cell9, "dataFormat", percentStyle) | ||||
| @@ -2587,11 +2653,18 @@ open class ReportService( | |||||
| return workbook | return workbook | ||||
| } | } | ||||
| fun genCostAndExpenseReport(request: costAndExpenseRequest): ByteArray{ | |||||
| fun genCostAndExpenseReport(request: costAndExpenseRequest): ByteArray { | |||||
| val costAndExpenseList = getCostAndExpense(request.clientId, request.teamId, request.type) | 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() | val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() | ||||
| workbook.write(outputStream) | workbook.write(outputStream) | ||||
| @@ -2599,8 +2672,16 @@ open class ReportService( | |||||
| return outputStream.toByteArray() | 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 | SELECT | ||||
| p.code AS project_code, | p.code AS project_code, | ||||
| p.name AS project_name, | p.name AS project_name, | ||||
| @@ -2621,7 +2702,8 @@ open class ReportService( | |||||
| p.status = 'Pending to Start' | p.status = 'Pending to Start' | ||||
| AND p.planStart < CURRENT_DATE | AND p.planStart < CURRENT_DATE | ||||
| AND m.endDate BETWEEN :remainedDate AND :remainedDateTo | AND m.endDate BETWEEN :remainedDate AND :remainedDateTo | ||||
| """.trimIndent()) | |||||
| """.trimIndent() | |||||
| ) | |||||
| if (teamId != null && teamId > 0) { | if (teamId != null && teamId > 0) { | ||||
| sql.append(" AND t.id = :teamId") | sql.append(" AND t.id = :teamId") | ||||
| @@ -2645,4 +2727,240 @@ open class ReportService( | |||||
| ) | ) | ||||
| return jdbcDao.queryForList(sql.toString(), args) | 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 | |||||
| } | |||||
| } | |||||
| @@ -34,8 +34,12 @@ import java.time.format.DateTimeFormatter | |||||
| import com.ffii.tsms.modules.project.entity.Project | import com.ffii.tsms.modules.project.entity.Project | ||||
| import com.ffii.tsms.modules.project.service.SubsidiaryService | import com.ffii.tsms.modules.project.service.SubsidiaryService | ||||
| import com.ffii.tsms.modules.report.web.model.* | 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.Example | ||||
| import org.springframework.data.domain.ExampleMatcher | import org.springframework.data.domain.ExampleMatcher | ||||
| import java.time.YearMonth | |||||
| import java.util.* | |||||
| @RestController | @RestController | ||||
| @RequestMapping("/reports") | @RequestMapping("/reports") | ||||
| @@ -55,9 +59,11 @@ class ReportController( | |||||
| private val customerService: CustomerService, | private val customerService: CustomerService, | ||||
| private val subsidiaryService: SubsidiaryService, | private val subsidiaryService: SubsidiaryService, | ||||
| private val invoiceService: InvoiceService, private val gradeAllocationRepository: GradeAllocationRepository, | 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") | @PostMapping("/fetchProjectsFinancialStatusReport") | ||||
| @Throws(ServletRequestBindingException::class, IOException::class) | @Throws(ServletRequestBindingException::class, IOException::class) | ||||
| fun getFinancialStatusReport(@RequestBody @Valid request: FinancialStatusReportRequest): ResponseEntity<Resource> { | fun getFinancialStatusReport(@RequestBody @Valid request: FinancialStatusReportRequest): ResponseEntity<Resource> { | ||||
| @@ -76,7 +82,8 @@ class ReportController( | |||||
| val project = projectRepository.findById(request.projectId).orElseThrow() | val project = projectRepository.findById(request.projectId).orElseThrow() | ||||
| val projectTasks = projectTaskRepository.findAllByProject(project) | 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 timesheets = timesheetRepository.findAllByProjectTaskIn(projectTasks) | ||||
| val reportResult: ByteArray = excelReportService.generateProjectCashFlowReport(project, invoices, timesheets, request.dateType) | val reportResult: ByteArray = excelReportService.generateProjectCashFlowReport(project, invoices, timesheets, request.dateType) | ||||
| @@ -300,4 +307,35 @@ class ReportController( | |||||
| .body(ByteArrayResource(reportResult)) | .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() | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -57,4 +57,8 @@ data class ProjectCompletionReport ( | |||||
| val startDate: LocalDate, | val startDate: LocalDate, | ||||
| val endDate: LocalDate, | val endDate: LocalDate, | ||||
| val outstanding: Boolean | val outstanding: Boolean | ||||
| ) | |||||
| data class CrossTeamChargeReportRequest ( | |||||
| val month: String, | |||||
| ) | ) | ||||
| @@ -14,6 +14,8 @@ interface TimesheetRepository : AbstractRepository<Timesheet, Long> { | |||||
| fun findAllByProjectTaskIn(projectTasks: List<ProjectTask>): List<Timesheet> | fun findAllByProjectTaskIn(projectTasks: List<ProjectTask>): List<Timesheet> | ||||
| fun findAllByProjectIn(project: List<Project>): List<Timesheet> | |||||
| fun deleteAllByStaffAndRecordDate(staff: Staff, recordDate: LocalDate) | 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") | @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") | ||||
| @@ -1,17 +1,22 @@ | |||||
| package com.ffii.tsms.modules.timesheet.service | package com.ffii.tsms.modules.timesheet.service | ||||
| import com.ffii.core.exception.BadRequestException | 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.Staff | ||||
| import com.ffii.tsms.modules.data.entity.StaffRepository | 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.StaffsService | ||||
| import com.ffii.tsms.modules.data.service.TeamService | 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.Timesheet | ||||
| import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository | 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.TeamMemberTimeEntries | ||||
| import com.ffii.tsms.modules.timesheet.web.models.TimeEntry | 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.stereotype.Service | ||||
| import org.springframework.transaction.annotation.Transactional | import org.springframework.transaction.annotation.Transactional | ||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| @@ -40,14 +45,15 @@ open class TimesheetsService( | |||||
| mergeTimeEntriesByProjectAndTask(timeEntries).map { timeEntry -> | mergeTimeEntriesByProjectAndTask(timeEntries).map { timeEntry -> | ||||
| val task = timeEntry.taskId?.let { taskRepository.findById(it).getOrNull() } | val task = timeEntry.taskId?.let { taskRepository.findById(it).getOrNull() } | ||||
| val project = timeEntry.projectId?.let { projectRepository.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 { | Timesheet().apply { | ||||
| this.staff = currentStaff | this.staff = currentStaff | ||||
| this.recordDate = entryDate | this.recordDate = entryDate | ||||
| this.normalConsumed = timeEntry.inputHours | this.normalConsumed = timeEntry.inputHours | ||||
| this.otConsumed = timeEntry.otHours | this.otConsumed = timeEntry.otHours | ||||
| this.projectTask = projectTask | |||||
| this.projectTask = projectTask | |||||
| this.project = project | this.project = project | ||||
| this.remark = timeEntry.remark | this.remark = timeEntry.remark | ||||
| } | } | ||||
| @@ -60,7 +66,11 @@ open class TimesheetsService( | |||||
| } | } | ||||
| @Transactional | @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() | val authorities = staffsService.currentAuthorities() ?: throw BadRequestException() | ||||
| if (!authorities.stream().anyMatch { it.authority.equals("MAINTAIN_TIMESHEET") }) { | 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 timesheet = timesheetRepository.findById(entry.id).getOrDefault(Timesheet()).apply { | ||||
| val task = entry.taskId?.let { taskRepository.findById(it).getOrNull() } | val task = entry.taskId?.let { taskRepository.findById(it).getOrNull() } | ||||
| val project = entry.projectId?.let { projectRepository.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.normalConsumed = entry.inputHours | ||||
| this.otConsumed = entry.otHours | this.otConsumed = entry.otHours | ||||
| this.projectTask = projectTask | |||||
| this.projectTask = projectTask | |||||
| this.project = project | this.project = project | ||||
| this.remark = entry.remark | this.remark = entry.remark | ||||
| this.recordDate = this.recordDate ?: recordDate | this.recordDate = this.recordDate ?: recordDate | ||||
| @@ -109,7 +119,7 @@ open class TimesheetsService( | |||||
| open fun getTeamMemberTimesheet(): Map<Long, TeamMemberTimeEntries> { | open fun getTeamMemberTimesheet(): Map<Long, TeamMemberTimeEntries> { | ||||
| val authorities = staffsService.currentAuthorities() ?: return emptyMap() | 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() | val currentStaff = staffsService.currentStaff() | ||||
| // Get team where current staff is team lead | // 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>> { | private fun transformToTimeEntryMap(timesheets: List<Timesheet>): Map<String, List<TimeEntry>> { | ||||
| return timesheets | return timesheets | ||||
| .groupBy { timesheet -> timesheet.recordDate!!.format(DateTimeFormatter.ISO_LOCAL_DATE) } | .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> { | private fun mergeTimeEntriesByProjectAndTask(entries: List<TimeEntry>): List<TimeEntry> { | ||||
| return entries | return entries | ||||
| .groupBy { timeEntry -> Pair(timeEntry.projectId, timeEntry.taskId) } | .groupBy { timeEntry -> Pair(timeEntry.projectId, timeEntry.taskId) } | ||||
| .values.map { timeEntries -> | .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" | |||||
| } | } | ||||
| } | } | ||||
| @@ -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.LeaveService | ||||
| import com.ffii.tsms.modules.timesheet.service.TimesheetsService | import com.ffii.tsms.modules.timesheet.service.TimesheetsService | ||||
| import com.ffii.tsms.modules.timesheet.web.models.* | import com.ffii.tsms.modules.timesheet.web.models.* | ||||
| import jakarta.servlet.http.HttpServletRequest | |||||
| import jakarta.validation.Valid | 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.GetMapping | ||||
| import org.springframework.web.bind.annotation.PostMapping | import org.springframework.web.bind.annotation.PostMapping | ||||
| import org.springframework.web.bind.annotation.RequestBody | import org.springframework.web.bind.annotation.RequestBody | ||||
| import org.springframework.web.bind.annotation.RequestMapping | import org.springframework.web.bind.annotation.RequestMapping | ||||
| import org.springframework.web.bind.annotation.RestController | import org.springframework.web.bind.annotation.RestController | ||||
| import org.springframework.web.multipart.MultipartHttpServletRequest | |||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
| @@ -85,4 +91,20 @@ class TimesheetsController(private val timesheetsService: TimesheetsService, pri | |||||
| fun leaveTypes(): List<LeaveType> { | fun leaveTypes(): List<LeaveType> { | ||||
| return leaveService.getLeaveTypes() | 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)) | |||||
| } | |||||
| } | } | ||||
| @@ -229,6 +229,13 @@ public class UserService extends AbstractBaseEntityService<User, Long, UserRepos | |||||
| List<Map<String, Integer>> authBatchDeleteValues = req.getRemoveAuthIds().stream() | List<Map<String, Integer>> authBatchDeleteValues = req.getRemoveAuthIds().stream() | ||||
| .map(authId -> Map.of("userId", (int)id, "authId", authId)) | .map(authId -> Map.of("userId", (int)id, "authId", authId)) | ||||
| .collect(Collectors.toList()); | .collect(Collectors.toList()); | ||||
| if (!authBatchDeleteValues.isEmpty()) { | |||||
| jdbcDao.batchUpdate( | |||||
| "DELETE FROM user_authority" | |||||
| + " WHERE userId = :userId ", | |||||
| // + "AND authId = :authId", | |||||
| authBatchDeleteValues); | |||||
| } | |||||
| if (!authBatchInsertValues.isEmpty()) { | if (!authBatchInsertValues.isEmpty()) { | ||||
| jdbcDao.batchUpdate( | jdbcDao.batchUpdate( | ||||
| "INSERT IGNORE INTO user_authority (userId, authId)" | "INSERT IGNORE INTO user_authority (userId, authId)" | ||||
| @@ -236,13 +243,6 @@ public class UserService extends AbstractBaseEntityService<User, Long, UserRepos | |||||
| authBatchInsertValues); | authBatchInsertValues); | ||||
| } | } | ||||
| if (!authBatchDeleteValues.isEmpty()) { | |||||
| jdbcDao.batchUpdate( | |||||
| "DELETE FROM user_authority" | |||||
| + " WHERE userId = :userId ", | |||||
| // + "AND authId = :authId", | |||||
| authBatchDeleteValues); | |||||
| } | |||||
| return instance; | return instance; | ||||
| } | } | ||||
| @@ -0,0 +1,9 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset cyril:task | |||||
| INSERT | |||||
| INTO | |||||
| task | |||||
| (name, taskGroupId) | |||||
| VALUES | |||||
| ('5.8 Manhour Import', 5); | |||||
| @@ -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 ; | |||||
| @@ -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); | |||||
| @@ -0,0 +1,5 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset cyril:project | |||||
| ALTER TABLE `project` | |||||
| ADD COLUMN `subContractFee` DOUBLE NULL DEFAULT NULL AFTER `expectedTotalFee`; | |||||