@@ -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`; |