@@ -3,4 +3,5 @@ package com.ffii.tsms.modules.data.entity; | |||
import com.ffii.core.support.AbstractRepository | |||
interface BuildingTypeRepository : AbstractRepository<BuildingType, Long> { | |||
fun findByName(name: String): BuildingType? | |||
} |
@@ -3,6 +3,7 @@ package com.ffii.tsms.modules.data.entity; | |||
import java.util.Optional; | |||
import java.util.List; | |||
import org.springframework.data.jpa.repository.Query; | |||
import org.springframework.data.repository.query.Param; | |||
import com.ffii.core.support.AbstractRepository; | |||
@@ -11,4 +12,7 @@ public interface CustomerRepository extends AbstractRepository<Customer, Long> { | |||
List<Customer> findAllByDeletedFalse(); | |||
Optional<Customer> findByCode(@Param("code") String code); | |||
Optional<Customer> findByName(@Param("name") String name); | |||
@Query("SELECT max(cast(substring_index(c.code, '-', -1) as long)) FROM Customer c") | |||
Long getLatestCodeNumber(); | |||
} |
@@ -3,4 +3,6 @@ package com.ffii.tsms.modules.data.entity; | |||
import com.ffii.core.support.AbstractRepository | |||
interface LocationRepository : AbstractRepository<Location, Long> { | |||
fun findByName(name: String): Location | |||
} |
@@ -25,4 +25,5 @@ public interface StaffRepository extends AbstractRepository<Staff, Long> { | |||
Optional<List<Staff>> findAllByDeletedFalse(); | |||
Optional<Staff> findIdAndNameByUserIdAndDeletedFalse(Long id); | |||
} |
@@ -10,6 +10,7 @@ import java.util.List; | |||
import static jakarta.persistence.CascadeType.ALL; | |||
@Entity | |||
@Table(name = "subsidiary") | |||
public class Subsidiary extends BaseEntity<Long> { | |||
@@ -1,6 +1,7 @@ | |||
package com.ffii.tsms.modules.data.entity; | |||
import com.ffii.core.support.AbstractRepository; | |||
import org.springframework.data.jpa.repository.Query; | |||
import org.springframework.data.repository.query.Param; | |||
import java.util.List; | |||
@@ -12,4 +13,9 @@ public interface SubsidiaryRepository extends AbstractRepository<Subsidiary, Lon | |||
List<Subsidiary> findAllByDeletedFalseAndIdIn(List<Long> id); | |||
Optional<Subsidiary> findByCode(@Param("code") String code); | |||
Optional<Subsidiary> findByName(@Param("name") String name); | |||
@Query("SELECT max(cast(substring_index(s.code, '-', -1) as long)) FROM Subsidiary s") | |||
Long getLatestCodeNumber(); | |||
} |
@@ -9,4 +9,6 @@ public interface TeamRepository extends AbstractRepository<Team, Long> { | |||
List<Team> findByDeletedFalse(); | |||
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 | |||
interface WorkNatureRepository : AbstractRepository<WorkNature, Long> { | |||
fun findByName(name: String): WorkNature? | |||
} |
@@ -37,6 +37,18 @@ open class CustomerService( | |||
return customerRepository.findByCode(code); | |||
} | |||
open fun createClientCode(): String { | |||
val prefix = "CT" | |||
val latestClientCode = customerRepository.getLatestCodeNumber() | |||
if (latestClientCode != null) { | |||
return "$prefix-" + String.format("%03d", latestClientCode + 1L) | |||
} else { | |||
return "$prefix-001" | |||
} | |||
} | |||
open fun saveCustomer(saveCustomer: SaveCustomerRequest): SaveCustomerResponse { | |||
val duplicateCustomer = findCustomerByCode(saveCustomer.code) | |||
@@ -68,19 +68,23 @@ open class DashboardService( | |||
fun searchCustomerSubsidiaryProject(args: Map<String, Any>): List<Map<String, Any>> { | |||
val sql = StringBuilder("select" | |||
+ " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id," | |||
+ " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id," | |||
+ " p.id as id," | |||
+ " p.id as projectId," | |||
+ " p.code as projectCode," | |||
+ " p.name as projectName," | |||
+ " te.code as team," | |||
+ " s.name as teamLead," | |||
+ " tg.name as expectedStage," | |||
+ " GROUP_CONCAT(DISTINCT tg.name ORDER BY tg.name) as expectedStage," | |||
+ " p.totalManhour as budgetedManhour," | |||
+ " sum(t.normalConsumed) + sum(t.otConsumed) as spentManhour," | |||
+ " p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed) as remainedManhour," | |||
+ " coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) as manhourConsumptionPercentage," | |||
+ " COALESCE (DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d'),'NA') as comingPaymentMilestone" | |||
+ " COALESCE (DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d'),'NA') as comingPaymentMilestone," | |||
+ " case" | |||
+ " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) > 0 then 0" | |||
+ " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) <= 0 then 1" | |||
+ " end as alert" | |||
+ " from project p" | |||
+ " left join project_task pt on p.id = pt.project_id" | |||
+ " left join timesheet t on pt.id = t.projectTaskId" | |||
@@ -105,7 +109,7 @@ open class DashboardService( | |||
+ " ) milestonePayment on milestonePayment.pid = p.id" | |||
+ " where p.customerId = :customerId" | |||
+ " and p.customerSubsidiaryId = :subsidiaryId" | |||
+ " and (tg.name != '5. Miscellaneous' or tg.name is null)" | |||
// + " and (tg.name != '5. Miscellaneous' or tg.name is null)" | |||
+ " and p.status not in (\"Pending to Start\",\"Completed\",\"Deleted\")" | |||
) | |||
@@ -116,25 +120,38 @@ open class DashboardService( | |||
} | |||
} | |||
sql.append(" group by p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone") | |||
sql.append(" group by p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone") | |||
if (args["tableSorting"] == "ProjectName") { | |||
sql.append(" ORDER BY p.name ASC") | |||
} else if (args["tableSorting"] == "PercentageASC") { | |||
sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) asc") | |||
} else if (args["tableSorting"] == "PercentageDESC") { | |||
sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) desc") | |||
} | |||
return jdbcDao.queryForList(sql.toString(), args) | |||
} | |||
fun searchCustomerNonSubsidiaryProject(args: Map<String, Any>): List<Map<String, Any>> { | |||
val sql = StringBuilder("select" | |||
+ " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id," | |||
+ " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id," | |||
+ " p.id as id," | |||
+ " p.id as projectId," | |||
+ " p.code as projectCode," | |||
+ " p.name as projectName," | |||
+ " te.code as team," | |||
+ " s.name as teamLead," | |||
+ " tg.name as expectedStage," | |||
+ " GROUP_CONCAT(DISTINCT tg.name ORDER BY tg.name) as expectedStage," | |||
+ " p.totalManhour as budgetedManhour," | |||
+ " COALESCE (sum(t.normalConsumed) + sum(t.otConsumed),0) as spentManhour," | |||
+ " COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) as remainedManhour," | |||
+ " coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) as manhourConsumptionPercentage," | |||
+ " COALESCE (DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d'),'NA') as comingPaymentMilestone" | |||
+ " COALESCE (DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d'),'NA') as comingPaymentMilestone," | |||
+ " case" | |||
+ " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) > 0 then 0" | |||
+ " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) <= 0 then 1" | |||
+ " end as alert" | |||
+ " from project p" | |||
+ " left join project_task pt on p.id = pt.project_id" | |||
+ " left join timesheet t on pt.id = t.projectTaskId" | |||
@@ -159,10 +176,16 @@ open class DashboardService( | |||
+ " where p.customerId = :customerId" | |||
+ " and isNull(p.customerSubsidiaryId)" | |||
+ " and p.status not in (\"Pending to Start\",\"Completed\",\"Deleted\")" | |||
+ " and (tg.name != '5. Miscellaneous' or tg.name is null)" | |||
+ " group by p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone" | |||
// + " and (tg.name != '5. Miscellaneous' or tg.name is null)" | |||
+ " group by p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone" | |||
) | |||
if (args["tableSorting"] == "ProjectName") { | |||
sql.append(" ORDER BY p.name ASC") | |||
} else if (args["tableSorting"] == "PercentageASC") { | |||
sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) asc") | |||
} else if (args["tableSorting"] == "PercentageDESC") { | |||
sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) desc") | |||
} | |||
return jdbcDao.queryForList(sql.toString(), args) | |||
} | |||
@@ -276,19 +299,23 @@ open class DashboardService( | |||
fun searchTeamProject(args: Map<String, Any>): List<Map<String, Any>> { | |||
val sql = StringBuilder( | |||
"select" | |||
+ " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id," | |||
+ " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id," | |||
+ " p.id as id," | |||
+ " p.id as projectId," | |||
+ " p.code as projectCode," | |||
+ " p.name as projectName," | |||
+ " te.code as team," | |||
+ " s.name as teamLead," | |||
+ " tg.name as expectedStage," | |||
+ " GROUP_CONCAT(DISTINCT tg.name ORDER BY tg.name) as expectedStage," | |||
+ " p.totalManhour as budgetedManhour," | |||
+ " COALESCE (sum(t.normalConsumed) + sum(t.otConsumed),0) as spentManhour," | |||
+ " COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) as remainedManhour," | |||
+ " coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) as manhourConsumptionPercentage," | |||
+ " COALESCE (DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d'),'NA') as comingPaymentMilestone" | |||
+ " COALESCE (DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d'),'NA') as comingPaymentMilestone," | |||
+ " case" | |||
+ " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) > 0 then 0" | |||
+ " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) <= 0 then 1" | |||
+ " end as alert" | |||
+ " from project p" | |||
+ " left join project_task pt on p.id = pt.project_id" | |||
+ " left join timesheet t on pt.id = t.projectTaskId" | |||
@@ -312,12 +339,111 @@ open class DashboardService( | |||
+ " ) milestonePayment on milestonePayment.pid = p.id" | |||
+ " where p.teamLead = :teamLeadId" | |||
+ " and p.status not in (\"Pending to Start\",\"Completed\",\"Deleted\")" | |||
+ " and (tg.name != '5. Miscellaneous' or tg.name is null)" | |||
+ " group by p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone" | |||
// + " and (tg.name != '5. Miscellaneous' or tg.name is null)" | |||
+ " group by p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone" | |||
) | |||
if (args["tableSorting"] == "ProjectName") { | |||
sql.append(" ORDER BY p.name ASC") | |||
} else if (args["tableSorting"] == "PercentageASC") { | |||
sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) asc") | |||
} else if (args["tableSorting"] == "PercentageDESC") { | |||
sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) desc") | |||
} | |||
return jdbcDao.queryForList(sql.toString(), args) | |||
} | |||
fun searchTeamConsumption(args: Map<String, Any>): List<Map<String, Any>> | |||
{ | |||
val sql = StringBuilder( | |||
// "select" | |||
// + " ROW_NUMBER() OVER (ORDER BY te.code, s.name, project.budgetedManhour) AS id," | |||
// + " te.code as team," | |||
// + " s.name as teamLead," | |||
// + " project.budgetedManhour as budgetedManhour," | |||
// + " COALESCE (sum(t.normalConsumed) + sum(t.otConsumed),0) as spentManhour," | |||
// + " COALESCE (project.budgetedManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) as remainedManhour," | |||
// + " coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/project.budgetedManhour)*100,2),0) as manhourConsumptionPercentage," | |||
// + " case" | |||
// + " when COALESCE (project.budgetedManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) > 0 then 0" | |||
// + " when COALESCE (project.budgetedManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) <= 0 then 1" | |||
// + " end as alert" | |||
// + " from team te" | |||
// + " left join project p on p.teamLead = te.teamLead" | |||
// + " left join project_task pt on p.id = pt.project_id" | |||
// + " left join timesheet t on pt.id = t.projectTaskId" | |||
// + " left join staff s on te.teamLead = s.id" | |||
// + " left join (" | |||
// + " select sum(p2.totalManhour) as budgetedManhour" | |||
// + " from team t2" | |||
// + " left join project p2 on p2.teamLead = t2.teamLead" | |||
// + " where p2.teamLead in (:teamIds)" | |||
// + " ) as project on 1 = 1" | |||
// + " where p.teamLead in (:teamIds)" | |||
// + " and p.status not in ('Pending to Start','Completed','Deleted')" | |||
// + " group by te.code, s.name, project.budgetedManhour" | |||
"select" | |||
+ " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id," | |||
+ " p.id as id," | |||
+ " p.id as projectId," | |||
+ " p.code as projectCode," | |||
+ " p.name as projectName," | |||
+ " te.code as team," | |||
+ " s.name as teamLead," | |||
+ " GROUP_CONCAT(DISTINCT tg.name ORDER BY tg.name) as expectedStage," | |||
+ " p.totalManhour as budgetedManhour," | |||
+ " COALESCE (sum(t.normalConsumed) + sum(t.otConsumed),0) as spentManhour," | |||
+ " COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) as remainedManhour," | |||
+ " coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) as manhourConsumptionPercentage," | |||
+ " COALESCE (DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d'),'NA') as comingPaymentMilestone," | |||
+ " case" | |||
+ " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) > 0 then 0" | |||
+ " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) <= 0 then 1" | |||
+ " end as alert" | |||
+ " from project p" | |||
+ " left join project_task pt on p.id = pt.project_id" | |||
+ " left join timesheet t on pt.id = t.projectTaskId" | |||
+ " left join team te on p.teamLead = te.teamLead" | |||
+ " left join staff s on te.teamLead = s.id" | |||
+ " left join milestone m on p.id = m.projectId and curdate() >= m.startDate and curdate() <= m.endDate" | |||
+ " left join task_group tg on m.taskGroupId = tg.id" | |||
+ " left join (" | |||
+ " SELECT pid, MIN(comingPaymentMilestone) AS comingPaymentMilestone" | |||
+ " FROM (" | |||
+ " SELECT p.id AS pid, mp.date AS comingPaymentMilestone" | |||
+ " FROM project p" | |||
+ " LEFT JOIN milestone m ON p.id = m.projectId" | |||
+ " LEFT JOIN milestone_payment mp ON m.id = mp.milestoneId" | |||
+ " WHERE p.teamLead in (:teamIds)" | |||
+ " AND p.status NOT IN ('Pending to Start', 'Completed', 'Deleted')" | |||
+ " AND mp.date >= CURDATE()" | |||
+ " ) AS subquery" | |||
+ " GROUP BY pid" | |||
+ " ORDER BY comingPaymentMilestone ASC" | |||
+ " ) milestonePayment on milestonePayment.pid = p.id" | |||
+ " where p.teamLead in (:teamIds)" | |||
+ " and p.status not in ('Pending to Start','Completed','Deleted')" | |||
+ " group by p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone" | |||
) | |||
// if (args["tableSorting"] == "ProjectName") { | |||
// sql.append(" ORDER BY te.code ASC") | |||
// } else if (args["tableSorting"] == "PercentageASC") { | |||
// sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/project.budgetedManhour)*100,2),0) asc") | |||
// } else if (args["tableSorting"] == "PercentageDESC") { | |||
// sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/project.budgetedManhour)*100,2),0) desc") | |||
// } | |||
if (args["tableSorting"] == "ProjectName") { | |||
sql.append(" ORDER BY p.name ASC") | |||
} else if (args["tableSorting"] == "PercentageASC") { | |||
sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) asc") | |||
} else if (args["tableSorting"] == "PercentageDESC") { | |||
sql.append(" ORDER BY coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) desc") | |||
} | |||
return jdbcDao.queryForList(sql.toString(), args) | |||
} | |||
fun searchFinancialSummaryCard(args: Map<String, Any>): List<Map<String, Any>> { | |||
val sql = StringBuilder( | |||
"select" | |||
@@ -327,13 +453,19 @@ open class DashboardService( | |||
+ " coalesce(pj.totalFee,0) as totalFee," | |||
+ " coalesce(pj.totalBudget,0) as totalBudget," | |||
+ " coalesce(sum(i.issueAmount),0) as totalInvoiced," | |||
+ " coalesce(pj.totalFee,0) - coalesce(sum(i.issueAmount),0) as unInvoiced," | |||
+ " coalesce(sum(i.paidAmount),0) as totalReceived," | |||
+ " round(expenditure.cumulativeExpenditure,2) as cumulativeExpenditure," | |||
+ " case" | |||
+ " when coalesce(round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2),0) >= 1 then 'Positive'" | |||
+ " when coalesce(round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2),0) < 1 then 'Negative'" | |||
+ " end as cashFlowStatus," | |||
+ " coalesce(round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2),0) as cpi" | |||
+ " coalesce(round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2),0) as cpi," | |||
+ " coalesce(round(coalesce(pj.totalFee,0) / (expenditure.cumulativeExpenditure),2),0) as projectedCpi," | |||
+ " case" | |||
+ " when coalesce(round(coalesce(pj.totalFee,0) / (expenditure.cumulativeExpenditure),2),0) >= 1 then 'Positive'" | |||
+ " when coalesce(round(coalesce(pj.totalFee,0) / (expenditure.cumulativeExpenditure),2),0) < 1 then 'Negative'" | |||
+ " end as projectedCashFlowStatus" | |||
+ " from team t" | |||
+ " left join (" | |||
+ " select" | |||
@@ -404,12 +536,18 @@ open class DashboardService( | |||
+ " round(sum(p.expectedTotalFee) * 0.8,2) as totalBudget," | |||
+ " round(expenditure.cumulativeExpenditure,2) as cumulativeExpenditure," | |||
+ " sum(i.issueAmount) as totalInvoiced," | |||
+ " sum(p.expectedTotalFee) - sum(i.issueAmount) as unInvoiced," | |||
+ " sum(i.paidAmount) as totalReceived," | |||
+ " case" | |||
+ " when round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2) >= 1 then 'Positive'" | |||
+ " when round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2) < 1 then 'Negative'" | |||
+ " end as cashFlowStatus," | |||
+ " round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2) as cpi" | |||
+ " round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2) as cpi," | |||
+ " round(sum(p.expectedTotalFee) / (expenditure.cumulativeExpenditure),2) as projectedCpi," | |||
+ " case" | |||
+ " when round(sum(p.expectedTotalFee) / (expenditure.cumulativeExpenditure),2) >= 1 then 'Positive'" | |||
+ " when round(sum(p.expectedTotalFee) / (expenditure.cumulativeExpenditure),2) < 1 then 'Negative'" | |||
+ " end as projectedCashFlowStatus" | |||
+ " from project p" | |||
+ " left join (" | |||
+ " select" | |||
@@ -445,16 +583,103 @@ open class DashboardService( | |||
return jdbcDao.queryForList(sql.toString(), args) | |||
} | |||
// " case" | |||
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) <= COALESCE(expenditure.cumulativeExpenditure,0) then coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) - coalesce(sum(i.issueAmount),0)" | |||
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) < coalesce(sum(i.issueAmount),0) then 0" | |||
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) >= coalesce(sum(i.issueAmount),0) then COALESCE(expenditure.cumulativeExpenditure,0) - coalesce(sum(i.issueAmount),0)" | |||
// + " end as totalUninvoiced" | |||
fun searchFinancialSummaryByClient(args: Map<String, Any>): List<Map<String, Any>> { | |||
val sql = StringBuilder( "select" | |||
+ " ROW_NUMBER() OVER (ORDER BY t.id, c.id) AS id," | |||
// val sql = StringBuilder( "select" | |||
// + " ROW_NUMBER() OVER (ORDER BY t.id, c.id) AS id," | |||
// + " t.id as teamId," | |||
// + " c.id as cid," | |||
// + " c.code as customerCode," | |||
// + " c.name as customerName," | |||
// + " count(p.name) as projectNo," | |||
// + " sum(p.expectedTotalFee) as totalFee," | |||
// + " round(sum(p.expectedTotalFee) * 0.8,2) as totalBudget," | |||
// + " COALESCE(round(expenditure.cumulativeExpenditure,2),0) as cumulativeExpenditure," | |||
// + " coalesce(sum(i.issueAmount),0) as totalInvoiced," | |||
// + " coalesce(sum(i.paidAmount),0) as totalReceived," | |||
// + " case" | |||
// + " when coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) >= 1 then 'Positive'" | |||
// + " when coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) < 1 then 'Negative'" | |||
// + " end as cashFlowStatus," | |||
// + " coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) as cpi," | |||
// + " case" | |||
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) <= COALESCE(expenditure.cumulativeExpenditure,0) then coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) - coalesce(sum(i.issueAmount),0)" | |||
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) < coalesce(sum(i.issueAmount),0) then 0" | |||
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) >= coalesce(sum(i.issueAmount),0) then COALESCE(expenditure.cumulativeExpenditure,0) - coalesce(sum(i.issueAmount),0)" | |||
// + " end as totalUninvoiced" | |||
// + " from team t" | |||
// + " left join project p on t.teamLead = p.teamLead" | |||
// + " left join customer c on p.customerId = c.id" | |||
// + " left join (" | |||
// + " select" | |||
// + " t3.id as tid," | |||
// + " c3.id as cid," | |||
// + " sum(i3.issueAmount) as issueAmount," | |||
// + " sum(i3.paidAmount) as paidAmount" | |||
// + " from team t3" | |||
// + " left join project p3 on t3.teamLead = p3.teamLead" | |||
// + " left join customer c3 on p3.customerId = c3.id" | |||
// + " left join invoice i3 on p3.code = i3.projectCode" | |||
// + " where t3.deleted = 0" | |||
// + " and p3.status = 'On-going'" | |||
// | |||
// ) | |||
// | |||
// if (args != null) { | |||
// if (args.containsKey("teamId")) | |||
// sql.append(" AND t3.id = :teamId"); | |||
// } | |||
// sql.append( " group by c3.id, t3.id" | |||
// + " ) as i on i.cid = c.id and i.tid = t.id" | |||
// + " left join (" | |||
// + " select" | |||
// + " r.teamId as teamId," | |||
// + " r.customerId as customerId," | |||
// + " sum(r.cumulativeExpenditure) as cumulativeExpenditure" | |||
// + " from (" | |||
// + " select" | |||
// + " t1.id as teamId," | |||
// + " c2.id as customerId," | |||
// + " (coalesce(sum(t2.normalConsumed),0) * s2.hourlyRate) + (coalesce(sum(t2.otConsumed),0) * s2.hourlyRate * 1.0) as cumulativeExpenditure," | |||
// + " s2.hourlyRate as hourlyRate," | |||
// + " sum(t2.normalConsumed) as normalConsumed," | |||
// + " sum(t2.otConsumed) as otConsumed" | |||
// + " from team t1" | |||
// + " left join project p2 on t1.teamLead = p2.teamLead" | |||
// + " left join customer c2 on p2.customerId = c2 .id" | |||
// + " left join project_task pt ON p2.id = pt.project_id" | |||
// + " left join timesheet t2 on pt.id = t2.projectTaskId" | |||
// + " left join staff s on t2.staffId = s.id" | |||
// + " left join salary s2 on s.salaryId = s2.salaryPoint" | |||
// + " where p2.status = 'On-going'" | |||
// + " and t2.id is not null" | |||
// + " group by s2.hourlyRate,t1.id,c2.id" | |||
// + " ) as r" | |||
// + " group by r.teamId, r.customerId" | |||
// + " ) as expenditure on expenditure.teamId = t.id and expenditure.customerId = c.id" | |||
// + " where t.deleted = 0" | |||
// + " and p.status = 'On-going'") | |||
// | |||
// if (args != null) { | |||
// if (args.containsKey("teamId")) | |||
// sql.append(" AND t.id = :teamId"); | |||
// } | |||
// sql.append(" group by t.id, c.id, c.code, c.name") | |||
val sql = StringBuilder( | |||
"select" | |||
+ " ROW_NUMBER() OVER (ORDER BY t.id, i.cid) AS id," | |||
+ " t.id as teamId," | |||
+ " c.id as cid," | |||
+ " c.code as customerCode," | |||
+ " c.name as customerName," | |||
+ " count(p.name) as projectNo," | |||
+ " sum(p.expectedTotalFee) as totalFee," | |||
+ " round(sum(p.expectedTotalFee) * 0.8,2) as totalBudget," | |||
+ " i.cid as cid," | |||
+ " i.customerCode as customerCode," | |||
+ " i.customerName as customerName," | |||
+ " p.projectNo as projectNo," | |||
+ " p.totalFee as totalFee," | |||
+ " p.totalBudget as totalBudget," | |||
+ " COALESCE(round(expenditure.cumulativeExpenditure,2),0) as cumulativeExpenditure," | |||
+ " coalesce(sum(i.issueAmount),0) as totalInvoiced," | |||
+ " coalesce(sum(i.paidAmount),0) as totalReceived," | |||
@@ -463,18 +688,23 @@ open class DashboardService( | |||
+ " when coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) < 1 then 'Negative'" | |||
+ " end as cashFlowStatus," | |||
+ " coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) as cpi," | |||
+ " coalesce(round(p.totalFee / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) as projectedCpi," | |||
+ " case" | |||
+ " when coalesce(round(p.totalFee / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) >= 1 then 'Positive'" | |||
+ " when coalesce(round(p.totalFee / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) < 1 then 'Negative'" | |||
+ " end as projectedCashFlowStatus," | |||
+ " case" | |||
+ " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) <= COALESCE(expenditure.cumulativeExpenditure,0) then coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) - coalesce(sum(i.issueAmount),0)" | |||
+ " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) < coalesce(sum(i.issueAmount),0) then 0" | |||
+ " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) >= coalesce(sum(i.issueAmount),0) then COALESCE(expenditure.cumulativeExpenditure,0) - coalesce(sum(i.issueAmount),0)" | |||
+ " when p.totalFee - sum(i.issueAmount) >= 0 then coalesce(round(p.totalFee - sum(i.issueAmount),2),0)" | |||
+ " when p.totalFee - sum(i.issueAmount) < 0 then 0" | |||
+ " when p.totalFee - sum(i.issueAmount) is null then 0" | |||
+ " end as totalUninvoiced" | |||
+ " from team t" | |||
+ " left join project p on t.teamLead = p.teamLead" | |||
+ " left join customer c on p.customerId = c.id" | |||
+ " left join (" | |||
+ " select" | |||
+ " t3.id as tid," | |||
+ " c3.id as cid," | |||
+ " c3.code as customerCode," | |||
+ " c3.name as customerName," | |||
+ " sum(i3.issueAmount) as issueAmount," | |||
+ " sum(i3.paidAmount) as paidAmount" | |||
+ " from team t3" | |||
@@ -483,15 +713,34 @@ open class DashboardService( | |||
+ " left join invoice i3 on p3.code = i3.projectCode" | |||
+ " where t3.deleted = 0" | |||
+ " and p3.status = 'On-going'" | |||
) | |||
if (args != null) { | |||
if (args.containsKey("teamId")) | |||
) | |||
if (args != null) { | |||
if (args.containsKey("teamId")) | |||
sql.append(" AND t3.id = :teamId"); | |||
} | |||
sql.append( " group by c3.id, t3.id" | |||
+ " ) as i on i.cid = c.id and i.tid = t.id" | |||
} | |||
sql.append( | |||
" group by c3.id, t3.id, customerCode, customerName" | |||
+ " ) as i on i.tid = t.id" | |||
+ " left join (" | |||
+ " select" | |||
+ " t.id as tid," | |||
+ " c.id as cid," | |||
+ " count(p.id) as projectNo," | |||
+ " sum(p.expectedTotalFee) as totalFee," | |||
+ " round(sum(p.expectedTotalFee) * 0.8,2) as totalBudget" | |||
+ " from team t" | |||
+ " left join project p on t.teamLead = p.teamLead" | |||
+ " left join customer c on p.customerId = c.id" | |||
+ " where t.deleted = 0" | |||
+ " and p.status = 'On-going'" | |||
) | |||
if (args != null) { | |||
if (args.containsKey("teamId")) | |||
sql.append(" AND t.id = :teamId"); | |||
} | |||
sql.append( | |||
" group by t.id, c.id" | |||
+ " ) as p on p.tid = t.id and p.cid = i.cid" | |||
+ " left join (" | |||
+ " select" | |||
+ " r.teamId as teamId," | |||
@@ -517,15 +766,15 @@ open class DashboardService( | |||
+ " group by s2.hourlyRate,t1.id,c2.id" | |||
+ " ) as r" | |||
+ " group by r.teamId, r.customerId" | |||
+ " ) as expenditure on expenditure.teamId = t.id and expenditure.customerId = c.id" | |||
+ " ) as expenditure on expenditure.teamId = t.id and expenditure.customerId = i.cid" | |||
+ " where t.deleted = 0" | |||
+ " and p.status = 'On-going'") | |||
if (args != null) { | |||
if (args.containsKey("teamId")) | |||
+ " and i.cid is not null" | |||
) | |||
if (args != null) { | |||
if (args.containsKey("teamId")) | |||
sql.append(" AND t.id = :teamId"); | |||
} | |||
sql.append(" group by t.id, c.id, c.code, c.name") | |||
} | |||
sql.append( " group by t.id, i.cid, i.customerCode, i.customerName, p.projectNo, p.totalFee, p.totalBudget") | |||
return jdbcDao.queryForList(sql.toString(), args) | |||
} | |||
@@ -547,10 +796,20 @@ open class DashboardService( | |||
+ " when coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) < 1 then 'Negative'" | |||
+ " end as cashFlowStatus," | |||
+ " coalesce(round(sum(i.issueAmount) / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) as cpi," | |||
+ " coalesce(round(p.expectedTotalFee / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) as projectedCpi," | |||
+ " case" | |||
+ " when coalesce(round(p.expectedTotalFee / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) >= 1 then 'Positive'" | |||
+ " when coalesce(round(p.expectedTotalFee / (COALESCE(expenditure.cumulativeExpenditure,0)),2),0) < 1 then 'Negative'" | |||
+ " end as projectedCashFlowStatus," | |||
// + " case" | |||
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) <= COALESCE(expenditure.cumulativeExpenditure,0) then coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) - coalesce(sum(i.issueAmount),0)" | |||
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) < coalesce(sum(i.issueAmount),0) then 0" | |||
// + " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) >= coalesce(sum(i.issueAmount),0) then COALESCE(expenditure.cumulativeExpenditure,0) - coalesce(sum(i.issueAmount),0)" | |||
// + " end as totalUninvoiced" | |||
+ " case" | |||
+ " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) <= COALESCE(expenditure.cumulativeExpenditure,0) then coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) - coalesce(sum(i.issueAmount),0)" | |||
+ " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) < coalesce(sum(i.issueAmount),0) then 0" | |||
+ " when coalesce(round(sum(p.expectedTotalFee) * 0.8,2),0) > COALESCE(expenditure.cumulativeExpenditure,0) and COALESCE(expenditure.cumulativeExpenditure,0) >= coalesce(sum(i.issueAmount),0) then COALESCE(expenditure.cumulativeExpenditure,0) - coalesce(sum(i.issueAmount),0)" | |||
+ " when p.expectedTotalFee - sum(i.issueAmount) >= 0 then coalesce(round(p.expectedTotalFee - sum(i.issueAmount),2),0)" | |||
+ " when p.expectedTotalFee - sum(i.issueAmount) < 0 then 0" | |||
+ " when p.expectedTotalFee - sum(i.issueAmount) is null then 0" | |||
+ " end as totalUninvoiced" | |||
+ " from team t" | |||
+ " left join project p on t.teamLead = p.teamLead" | |||
@@ -739,7 +998,9 @@ open class DashboardService( | |||
+ " coalesce (sum(i.issueAmount) - sum(i.paidAmount),0) as receivable," | |||
+ " coalesce (round(sum(p.expectedTotalFee)*0.8,2),0) as totalBudget," | |||
+ " coalesce (expenditure.expenditure,0) as totalExpenditure," | |||
+ " coalesce (sum(p.expectedTotalFee)*0.8 - expenditure.expenditure,0) as expenditureReceivable" | |||
+ " coalesce (sum(p.expectedTotalFee)*0.8 - expenditure.expenditure,0) as expenditureReceivable," | |||
+ " sum(p.expectedTotalFee) as totalProjectFee," | |||
+ " coalesce (round(sum(i.issueAmount)/sum(p.expectedTotalFee)*100,0),0) as invoicedPercentage" | |||
+ " from project p" | |||
+ " left join (" | |||
+ " select" | |||
@@ -1083,7 +1344,9 @@ open class DashboardService( | |||
"select" | |||
+ " concat(p.code,'-',p.name) as projectCodeAndName," | |||
+ " p.expectedTotalFee as totalFee," | |||
+ " expenditure.expenditure as expenditure," | |||
+ " p.expectedTotalFee * 0.8 as totalBudget," | |||
+ " coalesce (expenditure.expenditure,0) as expenditure," | |||
+ " (p.expectedTotalFee * 0.8) - coalesce (expenditure.expenditure,0) as remainingBudget," | |||
+ " case" | |||
+ " when p.expectedTotalFee - expenditure.expenditure >= 0 then 'Within Budget'" | |||
+ " when p.expectedTotalFee - expenditure.expenditure < 0 then 'Overconsumption'" | |||
@@ -1189,32 +1452,61 @@ open class DashboardService( | |||
+ " left join staff s on s.id = ts.staffId" | |||
+ " where p.id = :projectId" | |||
+ " group by p.id, t.id, t.name, g.name" | |||
+ " order by p.id;" | |||
+ " order by t.id asc, p.id;" | |||
) | |||
return jdbcDao.queryForList(sql.toString(), args) | |||
} | |||
fun monthlyActualTeamTotalManhoursSpent(args: Map<String, Any>): List<Map<String, Any>> { | |||
val sql = StringBuilder( | |||
" WITH RECURSIVE date_series AS (" | |||
+ " SELECT DATE_FORMAT(:startdate, '%Y-%m-01') AS month" | |||
+ " UNION ALL" | |||
+ " SELECT DATE_FORMAT(DATE_ADD(month, INTERVAL 1 MONTH), '%Y-%m-01')" | |||
+ " FROM date_series" | |||
+ " WHERE month < DATE_FORMAT(:enddate, '%Y-%m-01')" | |||
+ " )" | |||
+ " SELECT" | |||
+ " ds.month AS yearMonth," | |||
+ " IFNULL(SUM(IFNULL(ts.normalConsumed, 0) + IFNULL(ts.otConsumed, 0)), 0) AS 'TotalManhourConsumed'," | |||
+ " :teamId AS teamId" | |||
+ " FROM date_series ds" | |||
+ " LEFT JOIN staff st" | |||
+ " on st.teamId = :teamId" | |||
+ " LEFT JOIN timesheet ts" | |||
+ " ON DATE_FORMAT(ts.recordDate, '%Y-%m') = DATE_FORMAT(ds.month, '%Y-%m') and ts.staffID = st.id" | |||
+ " WHERE ds.month BETWEEN DATE_FORMAT(:startdate, '%Y-%m-01') AND DATE_FORMAT(:enddate, '%Y-%m-01')" | |||
+ " GROUP BY ds.month, st.teamId" | |||
+ " ORDER BY ds.month" | |||
// " WITH RECURSIVE date_series AS (" | |||
// + " SELECT DATE_FORMAT(:startdate, '%Y-%m-01') AS month" | |||
// + " UNION ALL" | |||
// + " SELECT DATE_FORMAT(DATE_ADD(month, INTERVAL 1 MONTH), '%Y-%m-01')" | |||
// + " FROM date_series" | |||
// + " WHERE month < DATE_FORMAT(:enddate, '%Y-%m-01')" | |||
// + " )" | |||
// + " SELECT" | |||
// + " ds.month AS yearMonth," | |||
// + " IFNULL(SUM(IFNULL(ts.normalConsumed, 0) + IFNULL(ts.otConsumed, 0)), 0) AS 'TotalManhourConsumed'," | |||
// + " :teamId AS teamId" | |||
// + " FROM date_series ds" | |||
// + " LEFT JOIN staff st" | |||
// + " on st.teamId = :teamId" | |||
// + " LEFT JOIN timesheet ts" | |||
// + " ON DATE_FORMAT(ts.recordDate, '%Y-%m') = DATE_FORMAT(ds.month, '%Y-%m') and ts.staffID = st.id" | |||
// + " WHERE ds.month BETWEEN DATE_FORMAT(:startdate, '%Y-%m-01') AND DATE_FORMAT(:enddate, '%Y-%m-01')" | |||
// + " and ts.recordDate BETWEEN DATE_FORMAT(:startdate, '%Y-%m-01') AND DATE_FORMAT(:enddate, '%Y-%m-01')" | |||
// + " GROUP BY ds.month, st.teamId" | |||
// + " ORDER BY ds.month" | |||
"WITH RECURSIVE date_series AS (" | |||
+ " SELECT DATE_FORMAT(:startdate, '%Y-%m-01') AS month" | |||
+ " UNION ALL" | |||
+ " SELECT DATE_FORMAT(DATE_ADD(month, INTERVAL 1 MONTH), '%Y-%m-01')" | |||
+ " FROM date_series" | |||
+ " WHERE month < DATE_FORMAT(:enddate, '%Y-%m-01')" | |||
+ " )" | |||
+ " SELECT" | |||
+ " ds.month AS yearMonth," | |||
+ " COALESCE(SUM(COALESCE(ts.normalConsumed, 0) + COALESCE(ts.otConsumed, 0)), 0) AS 'TotalManhourConsumed'," | |||
+ " :teamId AS teamId" | |||
+ " FROM date_series ds" | |||
+ " LEFT JOIN (" | |||
+ " SELECT" | |||
+ " DATE_FORMAT(ts.recordDate, '%Y-%m-01') AS recordMonth," | |||
+ " ts.staffID," | |||
+ " SUM(ts.normalConsumed) AS normalConsumed," | |||
+ " SUM(ts.otConsumed) AS otConsumed" | |||
+ " FROM staff st" | |||
+ " LEFT JOIN timesheet ts" | |||
+ " on ts.staffID = st.id" | |||
+ " WHERE ts.recordDate BETWEEN DATE_FORMAT(:startdate, '%Y-%m-01') AND LAST_DAY(DATE_FORMAT(:enddate, '%Y-%m-%d'))" | |||
+ " and st.teamId = :teamId" | |||
+ " GROUP BY DATE_FORMAT(ts.recordDate, '%Y-%m-01'), staffID" | |||
+ " ) ts ON ds.month = ts.recordMonth" | |||
+ " GROUP BY ds.month" | |||
+ " ORDER BY ds.month;" | |||
) | |||
return jdbcDao.queryForList(sql.toString(), args) | |||
@@ -1253,25 +1545,54 @@ open class DashboardService( | |||
} | |||
fun weeklyActualTeamTotalManhoursSpent(args: Map<String, Any>): List<Map<String, Any>> { | |||
val sql = StringBuilder( | |||
"WITH RECURSIVE date_series AS (" | |||
+ " SELECT DATE_FORMAT(:startdate, '%Y-%m-%d') AS day" | |||
+ " UNION ALL" | |||
+ " SELECT DATE_FORMAT(DATE_ADD(day, INTERVAL 1 DAY), '%Y-%m-%d')" | |||
+ " FROM date_series" | |||
+ " WHERE day < DATE_FORMAT(DATE_ADD(:startdate, INTERVAL 6 DAY), '%Y-%m-%d')" | |||
+ " )" | |||
+ " SELECT" | |||
+ " ds.day AS yearMonth," | |||
+ " IFNULL(SUM(IFNULL(ts.normalConsumed, 0) + IFNULL(ts.otConsumed, 0)), 0) AS 'TotalManhourConsumed'," | |||
+ " :teamId AS teamId" | |||
+ " FROM date_series ds" | |||
+ " LEFT JOIN staff st" | |||
+ " on st.teamId = :teamId" | |||
+ " LEFT JOIN timesheet ts" | |||
+ " ON DATE_FORMAT(ts.recordDate, '%m-%d') = DATE_FORMAT(ds.day, '%m-%d') and ts.staffID = st.id" | |||
+ " WHERE ds.day BETWEEN DATE_FORMAT(:startdate, '%Y-%m-%d') AND DATE_FORMAT(DATE_ADD(:startdate, INTERVAL 6 DAY), '%Y-%m-%d')" | |||
+ " GROUP BY ds.day, st.teamId" | |||
+ " ORDER BY ds.day" | |||
// "WITH RECURSIVE date_series AS (" | |||
// + " SELECT DATE_FORMAT(:startdate, '%Y-%m-%d') AS day" | |||
// + " UNION ALL" | |||
// + " SELECT DATE_FORMAT(DATE_ADD(day, INTERVAL 1 DAY), '%Y-%m-%d')" | |||
// + " FROM date_series" | |||
// + " WHERE day < DATE_FORMAT(DATE_ADD(:startdate, INTERVAL 6 DAY), '%Y-%m-%d')" | |||
// + " )" | |||
// + " SELECT" | |||
// + " ds.day AS yearMonth," | |||
// + " IFNULL(SUM(IFNULL(ts.normalConsumed, 0) + IFNULL(ts.otConsumed, 0)), 0) AS 'TotalManhourConsumed'," | |||
// + " :teamId AS teamId" | |||
// + " FROM date_series ds" | |||
// + " LEFT JOIN staff st" | |||
// + " on st.teamId = :teamId" | |||
// + " LEFT JOIN timesheet ts" | |||
// + " ON DATE_FORMAT(ts.recordDate, '%m-%d') = DATE_FORMAT(ds.day, '%m-%d') and ts.staffID = st.id" | |||
// + " WHERE ds.day BETWEEN DATE_FORMAT(:startdate, '%Y-%m-%d') AND DATE_FORMAT(DATE_ADD(:startdate, INTERVAL 6 DAY), '%Y-%m-%d')" | |||
// + " and ts.recordDate BETWEEN DATE_FORMAT(:startdate, '%Y-%m-%d') AND DATE_FORMAT(DATE_ADD(:startdate, INTERVAL 6 DAY), '%Y-%m-%d')" | |||
// + " GROUP BY ds.day, st.teamId" | |||
// + " ORDER BY ds.day" | |||
"WITH RECURSIVE date_series AS (" | |||
+ " SELECT DATE_FORMAT(:startdate, '%Y-%m-%d') AS day" | |||
+ " UNION ALL" | |||
+ " SELECT DATE_FORMAT(DATE_ADD(day, INTERVAL 1 DAY), '%Y-%m-%d')" | |||
+ " FROM date_series" | |||
+ " WHERE day < DATE_FORMAT(DATE_ADD(:startdate, INTERVAL 6 DAY), '%Y-%m-%d')" | |||
+ " )" | |||
+ " SELECT" | |||
+ " ds.day AS yearMonth," | |||
+ " COALESCE(SUM(COALESCE(ts.normalConsumed, 0) + COALESCE(ts.otConsumed, 0)), 0) AS 'TotalManhourConsumed'," | |||
+ " :teamId AS teamId" | |||
+ " FROM date_series ds" | |||
+ " LEFT JOIN (" | |||
+ " SELECT" | |||
+ " DATE_FORMAT(ts.recordDate, '%Y-%m-%d') AS recordDate," | |||
+ " ts.staffID," | |||
+ " SUM(ts.normalConsumed) AS normalConsumed," | |||
+ " SUM(ts.otConsumed) AS otConsumed" | |||
+ " FROM staff st" | |||
+ " LEFT JOIN timesheet ts" | |||
+ " on ts.staffID = st.id" | |||
+ " WHERE ts.recordDate BETWEEN DATE_FORMAT(:startdate, '%Y-%m-%d') AND DATE_FORMAT(DATE_ADD(:startdate, INTERVAL 6 DAY), '%Y-%m-%d')" | |||
+ " and st.teamId = :teamId" | |||
+ " GROUP BY DATE_FORMAT(ts.recordDate, '%Y-%m-%d'), ts.staffID" | |||
+ " ) ts ON ds.day = ts.recordDate" | |||
+ " GROUP BY ds.day" | |||
+ " ORDER BY ds.day;" | |||
) | |||
return jdbcDao.queryForList(sql.toString(), args) | |||
@@ -1346,6 +1667,7 @@ open class DashboardService( | |||
+ " AND dates.missing_date = ts.recordDate" | |||
+ " WHERE" | |||
+ " st.teamId = :teamId" | |||
+ " and st.deleted = 0" | |||
+ " AND ts.recordDate IS NULL" | |||
+ " GROUP BY st.id, st.name" | |||
+ " ORDER BY" | |||
@@ -1595,6 +1917,7 @@ open class DashboardService( | |||
+ " group by p.id, p.name" | |||
+ " ) as result on result.pid = p2.id" | |||
+ " where s2.id = :staffId" | |||
+ " and result.manhours > 0" | |||
+ " group by p2.id, p2.name, result.manhours" | |||
) | |||
@@ -1626,6 +1949,7 @@ open class DashboardService( | |||
+ " group by p.id, p.name" | |||
+ " ) as result on result.pid = p2.id" | |||
+ " where s2.id = :staffId" | |||
+ " and result.manhours > 0" | |||
+ " group by p2.id, p2.name, result.manhours" | |||
) | |||
@@ -1656,6 +1980,7 @@ open class DashboardService( | |||
+ " group by p.id, p.name" | |||
+ " ) as result on result.pid = p2.id" | |||
+ " where s2.id = :staffId" | |||
+ " and result.manhours > 0" | |||
+ " group by p2.id, p2.name, result.manhours" | |||
) | |||
@@ -38,6 +38,17 @@ open class SubsidiaryService( | |||
return subsidiaryRepository.findByCode(code); | |||
} | |||
open fun createSubsidiaryCode(): String { | |||
val prefix = "SY" | |||
val latestSubsidiaryCode = subsidiaryRepository.getLatestCodeNumber() | |||
if (latestSubsidiaryCode != null) { | |||
return "$prefix-" + String.format("%03d", latestSubsidiaryCode + 1L) | |||
} else { | |||
return "$prefix-001" | |||
} | |||
} | |||
open fun saveSubsidiary(saveSubsidiary: SaveSubsidiaryRequest): SaveSubsidiaryResponse { | |||
val duplicateSubsidiary = findSubsidiaryByCode(saveSubsidiary.code) | |||
@@ -51,6 +51,7 @@ class DashboardController( | |||
fun searchCustomerSubsidiaryProject(request: HttpServletRequest?): List<Map<String, Any>> { | |||
val customerId = request?.getParameter("customerId") | |||
val subsidiaryId = request?.getParameter("subsidiaryId") | |||
val tableSorting = request?.getParameter("tableSorting") | |||
val args = mutableMapOf<String, Any>() | |||
var result: List<Map<String, Any>> = emptyList() | |||
if (customerId != null) { | |||
@@ -59,6 +60,9 @@ class DashboardController( | |||
if (subsidiaryId != null) { | |||
args["subsidiaryId"] = subsidiaryId | |||
} | |||
if (tableSorting != null) { | |||
args["tableSorting"] = tableSorting | |||
} | |||
if (customerId != null && subsidiaryId != null) { | |||
result = dashboardService.searchCustomerSubsidiaryProject(args) | |||
@@ -80,16 +84,36 @@ class DashboardController( | |||
@GetMapping("/searchTeamProject") | |||
fun searchTeamProject(request: HttpServletRequest?): List<Map<String, Any>> { | |||
val teamLeadId = request?.getParameter("teamLeadId") | |||
val tableSorting = request?.getParameter("tableSorting") | |||
val args = mutableMapOf<String, Any>() | |||
var result: List<Map<String, Any>> = emptyList() | |||
if (teamLeadId != null) { | |||
args["teamLeadId"] = teamLeadId | |||
} | |||
if (tableSorting != null) { | |||
args["tableSorting"] = tableSorting | |||
} | |||
result = dashboardService.searchTeamProject(args) | |||
return result | |||
} | |||
@GetMapping("/searchTeamConsumption") | |||
fun searchTeamConsumption(request: HttpServletRequest?): List<Map<String, Any>> { | |||
val args = mutableMapOf<String, Any>() | |||
val teamIdList = request?.getParameter("teamIdList") | |||
val tableSorting = request?.getParameter("tableSorting") | |||
val teamIds = teamIdList?.split(",")?.map { it.toInt() }?.toList() | |||
var result: List<Map<String, Any>> = emptyList() | |||
if (teamIds != null) { | |||
args["teamIds"] = teamIds | |||
} | |||
if (tableSorting != null) { | |||
args["tableSorting"] = tableSorting | |||
} | |||
result = dashboardService.searchTeamConsumption(args) | |||
return result | |||
} | |||
@GetMapping("/searchFinancialSummaryCard") | |||
fun searchFinancialSummaryCard(request: HttpServletRequest?): List<Map<String, Any>> { | |||
val authority = dashboardService.viewDashboardAuthority() | |||
@@ -19,7 +19,7 @@ import org.springframework.web.bind.annotation.* | |||
open class SkillController(private val skillService: SkillService) { | |||
@PostMapping("/save") | |||
@PreAuthorize("hasAuthority('MAINTAIN_MASTERDATA')") | |||
// @PreAuthorize("hasAuthority('MAINTAIN_MASTERDATA')") | |||
open fun saveSkill(@Valid @RequestBody newSkill: NewSkillRequest): Skill { | |||
return skillService.saveOrUpdate(newSkill) | |||
} | |||
@@ -12,4 +12,6 @@ interface InvoiceRepository : AbstractRepository<Invoice, Long> { | |||
fun findInvoiceInfoByPaidAmountIsNotNull(): List<InvoiceInfo> | |||
fun findByInvoiceNo(invoiceNo: String): Invoice | |||
fun findAllByProjectCodeAndPaidAmountIsNotNull(projectCode: String): List<Invoice> | |||
} |
@@ -77,6 +77,9 @@ open class Project : BaseEntity<Long>() { | |||
@Column(name = "expectedTotalFee") | |||
open var expectedTotalFee: Double? = null | |||
@Column(name = "subContractFee") | |||
open var subContractFee: Double? = null | |||
@ManyToOne | |||
@JoinColumn(name = "serviceTypeId") | |||
open var serviceType: ServiceType? = null | |||
@@ -34,4 +34,6 @@ interface ProjectRepository : AbstractRepository<Project, Long> { | |||
fun findAllByStatusIsNotAndMainProjectIsNull(status: String): List<Project> | |||
fun findAllByTeamLeadAndCustomer(teamLead: Staff, customer: Customer): List<Project> | |||
fun findByCode(code: String): Project? | |||
} |
@@ -362,7 +362,7 @@ open class InvoiceService( | |||
// Check the import invoice with the data in DB | |||
for (i in 2..sheet.lastRowNum){ | |||
val sheetInvoice = ExcelUtils.getCell(sheet, i, 0).stringCellValue | |||
val sheetInvoice = ExcelUtils.getCell(sheet, i, 0).toString() | |||
val sheetProjectCode = ExcelUtils.getCell(sheet, i, 1).stringCellValue | |||
checkInvoiceNo(sheetInvoice, invoices, invoicesResult, true) | |||
checkProjectCode(sheetProjectCode, projects, newProjectCodes) | |||
@@ -398,7 +398,7 @@ open class InvoiceService( | |||
// val paymentMilestoneId = getMilestonePaymentId(ExcelUtils.getCell(sheet, i, 1).stringCellValue, ExcelUtils.getCell(sheet, i, 5).stringCellValue) | |||
// val milestonePayment = milestonePaymentRepository.findById(paymentMilestoneId).orElseThrow() | |||
val invoice = Invoice().apply { | |||
invoiceNo = ExcelUtils.getCell(sheet, i, 0).stringCellValue | |||
invoiceNo = ExcelUtils.getCell(sheet, i, 0).toString() | |||
projectCode = ExcelUtils.getCell(sheet, i, 1).stringCellValue | |||
projectName = ExcelUtils.getCell(sheet, i, 2).stringCellValue | |||
team = ExcelUtils.getCell(sheet, i, 3).stringCellValue | |||
@@ -447,7 +447,7 @@ open class InvoiceService( | |||
val duplicateItemsInInvoice = checkDuplicateItemInImportedInvoice(sheet,2,0) | |||
for (i in 2..sheet.lastRowNum){ | |||
val sheetInvoice = ExcelUtils.getCell(sheet, i, 0).stringCellValue | |||
val sheetInvoice = ExcelUtils.getCell(sheet, i, 0).toString() | |||
val sheetProjectCode = ExcelUtils.getCell(sheet, i, 1).stringCellValue | |||
checkInvoiceNo(sheetInvoice, invoices, invoicesResult, false) | |||
checkProjectCode(sheetProjectCode, projects, newProjectCodes) | |||
@@ -489,7 +489,7 @@ open class InvoiceService( | |||
} | |||
for (i in 2..sheet.lastRowNum){ | |||
val invoice = getInvoiceByInvoiceNo(ExcelUtils.getCell(sheet, i, 0).stringCellValue) | |||
val invoice = getInvoiceByInvoiceNo(ExcelUtils.getCell(sheet, i, 0).toString()) | |||
invoice.paidAmount = ExcelUtils.getCell(sheet, i, 5).numericCellValue.toBigDecimal() | |||
invoice.receiptDate = ExcelUtils.getCell(sheet, i, 4).dateCellValue.toInstant().atZone(ZoneId.systemDefault()).toLocalDate() | |||
saveAndFlush(invoice) | |||
@@ -1,26 +1,26 @@ | |||
package com.ffii.tsms.modules.project.service | |||
import com.ffii.core.utils.ExcelUtils | |||
import com.ffii.tsms.modules.common.SecurityUtils | |||
import com.ffii.tsms.modules.data.entity.* | |||
import com.ffii.tsms.modules.data.service.CustomerContactService | |||
import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo | |||
import com.ffii.tsms.modules.data.service.CustomerService | |||
import com.ffii.tsms.modules.data.service.GradeService | |||
import com.ffii.tsms.modules.data.service.SubsidiaryContactService | |||
import com.ffii.tsms.modules.data.service.* | |||
import com.ffii.tsms.modules.project.entity.* | |||
import com.ffii.tsms.modules.project.entity.Milestone | |||
import com.ffii.tsms.modules.project.entity.projections.InvoiceInfoSearchInfo | |||
import com.ffii.tsms.modules.project.entity.projections.InvoiceSearchInfo | |||
import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo | |||
import com.ffii.tsms.modules.project.web.models.* | |||
import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository | |||
import org.apache.commons.logging.LogFactory | |||
import org.apache.poi.ss.usermodel.Sheet | |||
import org.apache.poi.ss.usermodel.Workbook | |||
import org.springframework.stereotype.Service | |||
import org.springframework.transaction.annotation.Transactional | |||
import java.time.LocalDate | |||
import java.time.format.DateTimeFormatter | |||
import java.util.Optional | |||
import kotlin.jvm.optionals.getOrElse | |||
import kotlin.jvm.optionals.getOrNull | |||
@Service | |||
open class ProjectsService( | |||
private val projectRepository: ProjectRepository, | |||
@@ -46,7 +46,11 @@ open class ProjectsService( | |||
private val timesheetRepository: TimesheetRepository, | |||
private val taskTemplateRepository: TaskTemplateRepository, | |||
private val subsidiaryContactService: SubsidiaryContactService, | |||
private val subsidiaryContactRepository: SubsidiaryContactRepository | |||
private val subsidiaryContactRepository: SubsidiaryContactRepository, | |||
private val teamRepository: TeamRepository, | |||
private val customerRepository: CustomerRepository, | |||
private val subsidiaryRepository: SubsidiaryRepository, | |||
private val customerSubsidiaryService: CustomerSubsidiaryService, | |||
) { | |||
open fun allProjects(): List<ProjectSearchInfo> { | |||
return projectRepository.findProjectSearchInfoByOrderByCreatedDesc() | |||
@@ -107,7 +111,8 @@ open class ProjectsService( | |||
code = project.code!!, | |||
name = project.name!!, | |||
status = project.status, | |||
tasks = projectTaskRepository.findAllByProject(project).mapNotNull { pt -> pt.task }, | |||
tasks = projectTaskRepository.findAllByProject(project).mapNotNull { pt -> pt.task } | |||
.sortedBy { it.taskGroup?.id }, | |||
milestones = milestoneRepository.findAllByProject(project) | |||
.filter { milestone -> milestone.taskGroup?.id != null } | |||
.associateBy { milestone -> milestone.taskGroup!!.id!! }.mapValues { (_, milestone) -> | |||
@@ -164,10 +169,15 @@ open class ProjectsService( | |||
.orElseThrow() else null | |||
val teamLead = staffRepository.findById(request.projectLeadId).orElseThrow() | |||
val customer = customerService.findCustomer(request.clientId) | |||
val subsidiaryContact = subsidiaryContactRepository.findById(request.clientContactId).orElse(null) | |||
val clientContact = customerContactService.findByContactId(request.clientContactId) | |||
val subsidiaryContact = | |||
if (request.clientContactId != null) subsidiaryContactRepository.findById(request.clientContactId) | |||
.orElse(null) else null | |||
val clientContact = | |||
if (request.clientContactId != null) customerContactService.findByContactId(request.clientContactId) else null | |||
val customerSubsidiary = request.clientSubsidiaryId?.let { subsidiaryService.findSubsidiary(it) } | |||
val mainProject = if (request.mainProjectId != null && request.mainProjectId > 0) projectRepository.findById(request.mainProjectId).orElse(null) else null | |||
val mainProject = | |||
if (request.mainProjectId != null && request.mainProjectId > 0) projectRepository.findById(request.mainProjectId) | |||
.orElse(null) else null | |||
val allTasksMap = tasksService.allTasks().associateBy { it.id } | |||
val taskGroupMap = tasksService.allTaskGroups().associateBy { it.id } | |||
@@ -179,15 +189,23 @@ open class ProjectsService( | |||
project.apply { | |||
name = request.projectName | |||
description = request.projectDescription | |||
code = if (this.code.isNullOrEmpty() && request.mainProjectId == null) createProjectCode(request.isClpProject, project) else if (this.code.isNullOrEmpty() && request.mainProjectId != null && mainProject != null) createSubProjectCode(mainProject, project) else this.code | |||
code = request.projectCode | |||
?: if (this.code.isNullOrEmpty() && request.mainProjectId == null) createProjectCode( | |||
request.isClpProject, | |||
project | |||
) else if (this.code.isNullOrEmpty() && request.mainProjectId != null && mainProject != null) createSubProjectCode( | |||
mainProject, | |||
project | |||
) else this.code | |||
expectedTotalFee = request.expectedProjectFee | |||
subContractFee = request.subContractFee | |||
totalManhour = request.totalManhour | |||
actualStart = request.projectActualStart | |||
actualEnd = request.projectActualEnd | |||
status = if (this.status == "Deleted" || this.deleted == true) "Deleted" | |||
else if (this.actualStart != null && this.actualEnd != null) "Completed" | |||
else if (this.actualStart != null) "On-going" | |||
else "Pending To Start" | |||
else request.projectStatus ?: "Pending To Start" | |||
isClpProject = request.isClpProject | |||
this.mainProject = mainProject | |||
@@ -203,11 +221,11 @@ open class ProjectsService( | |||
this.teamLead = teamLead | |||
this.customer = customer | |||
custLeadName = | |||
if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact.name else subsidiaryContact.name | |||
if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact?.name else subsidiaryContact?.name | |||
custLeadEmail = | |||
if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact.email else subsidiaryContact.email | |||
if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact?.email else subsidiaryContact?.email | |||
custLeadPhone = | |||
if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact.phone else subsidiaryContact.phone | |||
if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact?.phone else subsidiaryContact?.phone | |||
this.customerSubsidiary = customerSubsidiary | |||
this.customerContact = if (customerSubsidiary == null) clientContact else null | |||
this.subsidiaryContact = if (customerSubsidiary != null) subsidiaryContact else null | |||
@@ -279,14 +297,15 @@ open class ProjectsService( | |||
if (milestones.isNotEmpty()) { | |||
project.apply { | |||
planStart = milestones.mapNotNull { it.startDate }.minOrNull() | |||
planEnd = milestones.mapNotNull { it.endDate }.maxOrNull() | |||
planEnd = request.projectPlanEnd ?: milestones.mapNotNull { it.endDate }.maxOrNull() | |||
} | |||
} | |||
val savedProject = projectRepository.save(project) | |||
val savedProject = projectRepository.saveAndFlush(project) | |||
val milestonesToDelete = milestoneRepository.findAllByProject(project).subtract(milestones.toSet()) | |||
val tasksToDelete = projectTaskRepository.findAllByProject(project).subtract(tasksToSave.toSet()) | |||
val mapTasksToSave = tasksToSave.map { it.task!!.id } | |||
val tasksToDelete = projectTaskRepository.findAllByProject(project).filter { !mapTasksToSave.contains(it.task!!.id) } | |||
val gradeAllocationsToDelete = | |||
gradeAllocationRepository.findByProject(project).subtract(gradeAllocations.toSet()) | |||
milestoneRepository.deleteAll(milestonesToDelete) | |||
@@ -386,7 +405,8 @@ open class ProjectsService( | |||
) | |||
}) | |||
}, | |||
expectedProjectFee = it.expectedTotalFee | |||
expectedProjectFee = it.expectedTotalFee, | |||
subContractFee = it.subContractFee | |||
) | |||
} | |||
} | |||
@@ -422,8 +442,9 @@ open class ProjectsService( | |||
val subsidiaryContact = project.customerSubsidiary?.id?.let { subsidiaryId -> | |||
subsidiaryContactService.findAllBySubsidiaryId(subsidiaryId) | |||
} ?: emptyList() | |||
val customerContact = project.customer?.id?.let { customerId -> customerContactService.findAllByCustomerId(customerId) } | |||
?: emptyList() | |||
val customerContact = | |||
project.customer?.id?.let { customerId -> customerContactService.findAllByCustomerId(customerId) } | |||
?: emptyList() | |||
MainProjectDetails( | |||
projectId = project.id, | |||
@@ -441,10 +462,308 @@ open class ProjectsService( | |||
buildingTypeIds = project.buildingTypes.mapNotNull { buildingType -> buildingType.id }, | |||
workNatureIds = project.workNatures.mapNotNull { workNature -> workNature.id }, | |||
clientId = project.customer?.id, | |||
clientContactId = subsidiaryContact.find { contact -> contact.name == project.custLeadName }?.id ?: customerContact.find { contact -> contact.name == project.custLeadName }?.id, | |||
clientContactId = subsidiaryContact.find { contact -> contact.name == project.custLeadName }?.id | |||
?: customerContact.find { contact -> contact.name == project.custLeadName }?.id, | |||
clientSubsidiaryId = project.customerSubsidiary?.id, | |||
expectedProjectFee = project.expectedTotalFee | |||
expectedProjectFee = project.expectedTotalFee, | |||
subContractFee = project.subContractFee, | |||
) | |||
} | |||
} | |||
@Transactional(rollbackFor = [Exception::class]) | |||
open fun importFile(workbook: Workbook?): String { | |||
val logger = LogFactory.getLog(javaClass) | |||
if (workbook == null) { | |||
return "No Excel import" // if workbook is null | |||
} | |||
val sheet: Sheet = workbook.getSheetAt(0) | |||
for (i in 2..<sheet.lastRowNum) { | |||
val row = sheet.getRow(i) | |||
if (row?.getCell(0) != null && !row.getCell(0).stringCellValue.isNullOrBlank()) { | |||
logger.info("row :$i | lastCellNum" + row.lastCellNum) | |||
// process client | |||
logger.info("---------client-------") | |||
logger.info(customerRepository.findByName(row.getCell(5).stringCellValue.trim())) | |||
val tempClient = customerRepository.findByName(row.getCell(5).stringCellValue.trim()) | |||
val currentClient = | |||
if (tempClient.isPresent) tempClient.orElseThrow() else customerService.saveCustomer( | |||
SaveCustomerRequest( | |||
code = customerService.createClientCode(), | |||
name = ExcelUtils.getStringValue(row.getCell(5)), | |||
typeId = ExcelUtils.getIntValue(row.getCell(6)).toLong(), | |||
deleteSubsidiaryIds = mutableListOf(), | |||
deleteContactIds = mutableListOf(), | |||
addSubsidiaryIds = mutableListOf(), | |||
addContacts = mutableListOf(), | |||
address = null, | |||
brNo = null, | |||
id = null, | |||
district = null, | |||
) | |||
).customer | |||
val clientId = currentClient.id!! | |||
logger.info("customer: $clientId") | |||
// process project | |||
logger.info("---------project-------") | |||
val projectTeamLead = teamRepository.findByCode(ExcelUtils.getStringValue(row.getCell(4))).staff | |||
var mainProjectId: Long? = null | |||
var projectCode = StringBuilder(row.getCell(0).stringCellValue).insert(1, '-').toString() | |||
if (projectCode.contains('(')) { | |||
val splitProjectCode = projectCode.split('(') | |||
val splitMainProjectCode = splitProjectCode[0].split('-') | |||
logger.info("splitProjectCode: " + splitProjectCode[1].split(')')[0]) | |||
projectCode = | |||
splitMainProjectCode[0] + '-' + String.format( | |||
"%04d", | |||
splitMainProjectCode[1].toInt() | |||
) + '-' + String.format("%03d", splitProjectCode[1].split(')')[0].toInt()) | |||
val mainProject = | |||
projectRepository.findByCode(splitProjectCode[0]) ?: projectRepository.saveAndFlush( | |||
Project().apply { | |||
name = row.getCell(1).stringCellValue | |||
description = row.getCell(1).stringCellValue | |||
code = splitMainProjectCode[0] + '-' + String.format( | |||
"%04d", | |||
splitMainProjectCode[1].toInt() | |||
) | |||
status = "Completed" | |||
projectCategory = projectCategoryRepository.findById(1).orElseThrow() | |||
customer = currentClient | |||
teamLead = projectTeamLead | |||
}) | |||
mainProjectId = mainProject.id | |||
} else { | |||
val splitProjectCode = projectCode.split('-') | |||
projectCode = splitProjectCode[0] + '-' + String.format("%04d", splitProjectCode[1].toInt()) | |||
} | |||
val project = projectRepository.findByCode(projectCode) | |||
val projectId = project?.id | |||
logger.info("projectCode :$projectCode") | |||
// process subsidiary | |||
logger.info("---------subsidiary-------") | |||
val subsidiary = if (row.getCell(7) != null && !row.getCell(7).stringCellValue.isNullOrBlank()) { | |||
val tempSubsidiary = subsidiaryRepository.findByName(ExcelUtils.getStringValue(row.getCell(7))) | |||
if (tempSubsidiary.isPresent) { | |||
tempSubsidiary.orElseThrow() | |||
} else { | |||
subsidiaryRepository.findByName(ExcelUtils.getStringValue(row.getCell(7))).orElse( | |||
subsidiaryService.saveSubsidiary( | |||
SaveSubsidiaryRequest( | |||
code = subsidiaryService.createSubsidiaryCode(), | |||
name = ExcelUtils.getStringValue(row.getCell(7)), | |||
typeId = ExcelUtils.getIntValue(row.getCell(8)).toLong(), | |||
brNo = null, | |||
address = null, | |||
district = null, | |||
deleteContactIds = mutableListOf(), | |||
addContacts = mutableListOf(), | |||
deleteCustomerIds = mutableListOf(), | |||
addCustomerIds = mutableListOf(clientId), | |||
id = null | |||
) | |||
).subsidiary | |||
) | |||
} | |||
} else null | |||
if (subsidiary != null) { | |||
if (!customerSubsidiaryService.findAllCustomerIdsBySubsidiaryId(subsidiary.id!!) | |||
.contains(clientId) | |||
) { | |||
subsidiaryService.saveSubsidiary( | |||
SaveSubsidiaryRequest( | |||
code = subsidiary.code, | |||
name = subsidiary.name, | |||
typeId = subsidiary.subsidiaryType.id!!, | |||
brNo = subsidiary.brNo, | |||
address = subsidiary.address, | |||
district = subsidiary.district, | |||
deleteContactIds = mutableListOf(), | |||
addContacts = mutableListOf(), | |||
deleteCustomerIds = mutableListOf(), | |||
addCustomerIds = mutableListOf(clientId), | |||
id = subsidiary.id | |||
) | |||
) | |||
} | |||
} | |||
// process building type | |||
logger.info("---------building type-------") | |||
var buildingType = buildingTypeRepository.findByName(row.getCell(13).stringCellValue) | |||
if (buildingType == null) { | |||
buildingType = | |||
buildingTypeRepository.saveAndFlush(BuildingType().apply { | |||
name = row.getCell(13).stringCellValue | |||
}) | |||
} | |||
// process work nature | |||
logger.info("---------work nature-------") | |||
var workNature = workNatureRepository.findByName(row.getCell(14).stringCellValue) | |||
if (workNature == null) { | |||
workNature = | |||
workNatureRepository.saveAndFlush(WorkNature().apply { name = row.getCell(14).stringCellValue }) | |||
} | |||
// process staff - staff from column R (17) | |||
logger.info("---------staff-------") | |||
val allocatedStaffIds = mutableListOf<Long>() | |||
for (j in 18..<row.lastCellNum step 2) { | |||
logger.info("j: $j | staffId: " + row.getCell(j).stringCellValue) | |||
val tempStaffId = row.getCell(j).stringCellValue | |||
if (!tempStaffId.isNullOrBlank()) { | |||
allocatedStaffIds.add(staffRepository.findByStaffId(tempStaffId).orElseThrow().id!!) | |||
} | |||
} | |||
// get task template - TW-Full QS - stage 5 | |||
logger.info("---------task template-------") | |||
val taskTemplate = taskTemplateRepository.findById(3).orElseThrow() | |||
val tasks = taskTemplate.tasks.groupBy { it.taskGroup!!.id } | |||
val groups = taskTemplate.groupAllocations.associateBy( | |||
keySelector = { it.taskGroup!!.id!! }, | |||
valueTransform = { it.percentage!! } | |||
) | |||
val projectTasks = if (project != null) projectTaskRepository.findAllByProject(project) else mutableListOf() | |||
var groupedProjectTasks = mapOf<Long, List<Long>>() | |||
if (projectTasks.isNotEmpty()) { | |||
groupedProjectTasks = projectTasks.groupBy { | |||
it.task!!.taskGroup!!.id!! | |||
}.mapValues { (_, projectTask) -> | |||
projectTask.map { it.task!!.id!! } | |||
} | |||
} | |||
val taskGroups = mutableMapOf<Long, TaskGroupAllocation>() | |||
groups.entries.forEach{ (key, value) -> | |||
var taskIds = tasks[key]!!.map { it.id!! } | |||
if (groupedProjectTasks.isNotEmpty() && !groupedProjectTasks[key].isNullOrEmpty()) { | |||
taskIds = (taskIds + groupedProjectTasks[key]!!).distinct() | |||
} | |||
if (key == 5L) { | |||
taskGroups[key] = TaskGroupAllocation(taskIds.plus(41).distinct(), value) | |||
logger.info("taskGroups[${key}]: ${taskGroups[key]}") | |||
} else { | |||
taskGroups[key] = TaskGroupAllocation(taskIds, value) | |||
logger.info("taskGroups[${key}]: ${taskGroups[key]}") | |||
} | |||
} | |||
logger.info("---------Import-------") | |||
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") | |||
val startDate = ExcelUtils.getDateValue( | |||
row.getCell(2), | |||
DateTimeFormatter.ofPattern("MM/dd/yyyy") | |||
).format(formatter) | |||
val endDate = ExcelUtils.getDateValue( | |||
row.getCell(3), | |||
DateTimeFormatter.ofPattern("MM/dd/yyyy") | |||
).format(formatter) | |||
saveProject( | |||
NewProjectRequest( | |||
projectCode = projectCode, | |||
projectName = row.getCell(1).stringCellValue, | |||
projectDescription = row.getCell(1).stringCellValue, | |||
projectActualStart = ExcelUtils.getDateValue( | |||
row.getCell(2), | |||
DateTimeFormatter.ofPattern("MM/dd/yyyy") | |||
), | |||
projectPlanEnd = ExcelUtils.getDateValue( | |||
row.getCell(3), | |||
DateTimeFormatter.ofPattern("MM/dd/yyyy") | |||
), | |||
projectLeadId = projectTeamLead.id!!, | |||
clientId = clientId, | |||
clientSubsidiaryId = subsidiary?.id, | |||
expectedProjectFee = row.getCell(9).numericCellValue, | |||
subContractFee = null, | |||
totalManhour = row.getCell(11).numericCellValue, | |||
locationId = 1, // HK | |||
buildingTypeIds = mutableListOf(buildingType!!.id!!), | |||
workNatureIds = mutableListOf(workNature!!.id!!), | |||
serviceTypeId = 9, | |||
fundingTypeId = row.getCell(16).numericCellValue.toLong(), | |||
allocatedStaffIds = allocatedStaffIds, | |||
isClpProject = projectCode[0] != 'M', | |||
mainProjectId = mainProjectId, | |||
projectCategoryId = 1, | |||
contractTypeId = if (row.getCell(14).stringCellValue == "Maintenance Term Contract") 5 else 2, | |||
projectId = projectId, | |||
projectStatus = "On-going", | |||
clientContactId = null, | |||
isSubsidiaryContact = subsidiary?.id != null && subsidiary.id!! > 0, | |||
manhourPercentageByGrade = taskTemplate.gradeAllocations.associateBy( | |||
keySelector = { it.grade!!.id!! }, | |||
valueTransform = { it.percentage!! } | |||
), | |||
projectActualEnd = null, | |||
taskTemplateId = taskTemplate.id, | |||
taskGroups = taskGroups, | |||
milestones = mapOf( | |||
Pair( | |||
1, com.ffii.tsms.modules.project.web.models.Milestone( | |||
startDate = startDate, | |||
endDate = endDate, | |||
payments = mutableListOf() | |||
) | |||
), | |||
Pair( | |||
2, com.ffii.tsms.modules.project.web.models.Milestone( | |||
startDate = startDate, | |||
endDate = endDate, | |||
payments = mutableListOf() | |||
) | |||
), | |||
Pair( | |||
3, com.ffii.tsms.modules.project.web.models.Milestone( | |||
startDate = startDate, | |||
endDate = endDate, | |||
payments = mutableListOf() | |||
) | |||
), | |||
Pair( | |||
4, com.ffii.tsms.modules.project.web.models.Milestone( | |||
startDate = startDate, | |||
endDate = endDate, | |||
payments = mutableListOf() | |||
) | |||
), | |||
Pair( | |||
5, com.ffii.tsms.modules.project.web.models.Milestone( | |||
startDate = startDate, | |||
endDate = endDate, | |||
payments = mutableListOf( | |||
PaymentInputs( | |||
-1, "Manhour Import", ExcelUtils.getDateValue( | |||
row.getCell(2), | |||
DateTimeFormatter.ofPattern("MM/dd/yyyy") | |||
).toString(), row.getCell(9).numericCellValue | |||
) | |||
) | |||
) | |||
), | |||
)) | |||
) | |||
} | |||
} | |||
return if (sheet.lastRowNum > 0) "Import Excel success" else "Import Excel failure" | |||
} | |||
} |
@@ -2,15 +2,21 @@ package com.ffii.tsms.modules.project.web | |||
import com.ffii.core.exception.NotFoundException | |||
import com.ffii.tsms.modules.data.entity.* | |||
import com.ffii.tsms.modules.project.entity.Project | |||
import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo | |||
import com.ffii.tsms.modules.project.entity.ProjectCategory | |||
import com.ffii.tsms.modules.project.entity.ProjectRepository | |||
import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo | |||
import com.ffii.tsms.modules.project.service.ProjectsService | |||
import com.ffii.tsms.modules.project.web.models.* | |||
import jakarta.servlet.http.HttpServletRequest | |||
import jakarta.validation.Valid | |||
import org.apache.poi.ss.usermodel.Workbook | |||
import org.apache.poi.xssf.usermodel.XSSFWorkbook | |||
import org.springframework.http.HttpStatus | |||
import org.springframework.http.ResponseEntity | |||
import org.springframework.web.bind.ServletRequestBindingException | |||
import org.springframework.web.bind.annotation.* | |||
import org.springframework.web.multipart.MultipartHttpServletRequest | |||
@RestController | |||
@RequestMapping("/projects") | |||
@@ -85,4 +91,20 @@ class ProjectsController(private val projectsService: ProjectsService, private v | |||
fun projectWorkNatures(): List<WorkNature> { | |||
return projectsService.allWorkNatures() | |||
} | |||
@PostMapping("/import") | |||
@Throws(ServletRequestBindingException::class) | |||
fun importFile(request: HttpServletRequest): ResponseEntity<*> { | |||
var workbook: Workbook? = null | |||
try { | |||
val multipartFile = (request as MultipartHttpServletRequest).getFile("multipartFileList") | |||
workbook = XSSFWorkbook(multipartFile?.inputStream) | |||
} catch (e: Exception) { | |||
println("Excel Wrong") | |||
println(e) | |||
} | |||
return ResponseEntity.ok(projectsService.importFile(workbook)) | |||
} | |||
} |
@@ -40,5 +40,6 @@ data class EditProjectDetails( | |||
val milestones: Map<Long, Milestone>, | |||
// Miscellaneous | |||
val expectedProjectFee: Double? | |||
val expectedProjectFee: Double?, | |||
val subContractFee: Double? | |||
) |
@@ -27,4 +27,5 @@ data class MainProjectDetails ( | |||
val clientSubsidiaryId: Long?, | |||
val expectedProjectFee: Double?, | |||
val subContractFee: Double?, | |||
) |
@@ -7,7 +7,7 @@ import java.time.LocalDate | |||
data class NewProjectRequest( | |||
// Project details | |||
// @field:NotBlank(message = "project code cannot be empty") | |||
// val projectCode: String, | |||
val projectCode: String?, | |||
@field:NotBlank(message = "project name cannot be empty") | |||
val projectName: String, | |||
val projectCategoryId: Long, | |||
@@ -16,8 +16,10 @@ data class NewProjectRequest( | |||
val projectId: Long?, | |||
val projectActualStart: LocalDate?, | |||
val projectActualEnd: LocalDate?, | |||
val projectPlanEnd: LocalDate?, | |||
val isClpProject: Boolean?, | |||
val mainProjectId: Long?, | |||
val projectStatus: String?, | |||
val serviceTypeId: Long, | |||
val fundingTypeId: Long, | |||
@@ -30,7 +32,7 @@ data class NewProjectRequest( | |||
// Client details | |||
val clientId: Long, | |||
val clientContactId: Long, | |||
val clientContactId: Long?, | |||
val clientSubsidiaryId: Long?, | |||
// Allocation | |||
@@ -43,7 +45,8 @@ data class NewProjectRequest( | |||
val milestones: Map<Long, Milestone>, | |||
// Miscellaneous | |||
val expectedProjectFee: Double | |||
val expectedProjectFee: Double, | |||
val subContractFee: Double? | |||
) | |||
data class TaskGroupAllocation( | |||
@@ -2,6 +2,7 @@ package com.ffii.tsms.modules.report.service | |||
import com.ffii.core.support.JdbcDao | |||
import com.ffii.tsms.modules.data.entity.Customer | |||
import com.ffii.tsms.modules.data.entity.Grade | |||
import com.ffii.tsms.modules.data.entity.Salary | |||
import com.ffii.tsms.modules.data.entity.Staff | |||
import com.ffii.tsms.modules.data.entity.Team | |||
@@ -17,6 +18,8 @@ import org.apache.poi.ss.util.CellRangeAddress | |||
import org.apache.poi.ss.util.CellUtil | |||
import org.apache.poi.xssf.usermodel.XSSFWorkbook | |||
import org.apache.poi.ss.usermodel.FormulaEvaluator | |||
import org.apache.poi.ss.util.CellReference | |||
import org.apache.poi.ss.util.RegionUtil | |||
import org.springframework.core.io.ClassPathResource | |||
import org.springframework.stereotype.Service | |||
import java.io.ByteArrayOutputStream | |||
@@ -54,6 +57,7 @@ open class ReportService( | |||
private val COMPLETE_PROJECT_OUTSTANDING_RECEIVABLE = | |||
"templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx" | |||
private val COMPLETION_PROJECT = "templates/report/AR05_Project Completion Report.xlsx" | |||
private val CROSS_TEAM_CHARGE_REPORT = "templates/report/Cross Team Charge Report.xlsx" | |||
// ==============================|| GENERATE REPORT ||============================== // | |||
fun generalCreateReportIndexed( // just loop through query records one by one, return rowIndex | |||
@@ -65,7 +69,7 @@ open class ReportService( | |||
var rowIndex = startRow | |||
var columnIndex = startColumn | |||
result.forEachIndexed { index, obj -> | |||
var tempCell = sheet.getRow(rowIndex).createCell(columnIndex) | |||
var tempCell = (sheet.getRow(rowIndex) ?: sheet.createRow(rowIndex)).createCell(columnIndex) | |||
tempCell.setCellValue((index + 1).toDouble()) | |||
val keys = obj.keys.toList() | |||
keys.forEachIndexed { keyIndex, key -> | |||
@@ -299,6 +303,25 @@ open class ReportService( | |||
return outputStream.toByteArray() | |||
} | |||
@Throws(IOException::class) | |||
fun generateCrossTeamChargeReport( | |||
month: String, | |||
timesheets: List<Timesheet>, | |||
teams: List<Team>, | |||
grades: List<Grade>, | |||
): ByteArray { | |||
// Generate the Excel report with query results | |||
val workbook: Workbook = | |||
createCrossTeamChargeReport(month, timesheets, teams, grades, CROSS_TEAM_CHARGE_REPORT) | |||
// Write the workbook to a ByteArrayOutputStream | |||
val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() | |||
workbook.write(outputStream) | |||
workbook.close() | |||
return outputStream.toByteArray() | |||
} | |||
// ==============================|| CREATE REPORT ||============================== // | |||
// EX01 Financial Report | |||
@@ -435,7 +458,8 @@ open class ReportService( | |||
val uninvoiceCell = row.createCell(12) | |||
uninvoiceCell.apply { | |||
cellFormula = | |||
" IF(I${rowNum}<=J${rowNum}, I${rowNum}-L${rowNum}, IF(AND(I${rowNum}>J${rowNum}, J${rowNum}<L${rowNum}), 0, IF(AND(I${rowNum}>J${rowNum}, J${rowNum}>=L${rowNum}), J${rowNum}-L${rowNum}, 0))) " | |||
" IF(H${rowNum}-L${rowNum}<0, 0, H${rowNum}-L${rowNum})" | |||
// " IF(I${rowNum}<=J${rowNum}, I${rowNum}-L${rowNum}, IF(AND(I${rowNum}>J${rowNum}, J${rowNum}<L${rowNum}), 0, IF(AND(I${rowNum}>J${rowNum}, J${rowNum}>=L${rowNum}), J${rowNum}-L${rowNum}, 0))) " | |||
cellStyle.dataFormat = accountingStyle | |||
} | |||
@@ -528,6 +552,11 @@ open class ReportService( | |||
CellUtil.setCellStyleProperty(sumUInvoiceCell, "borderBottom", BorderStyle.DOUBLE) | |||
val lastCpiCell = row.createCell(13) | |||
lastCpiCell.apply { | |||
cellFormula = "SUM(N15:N${rowNum})" | |||
cellStyle = boldFontCellStyle | |||
cellStyle.dataFormat = accountingStyle | |||
} | |||
CellUtil.setCellStyleProperty(lastCpiCell, "borderTop", BorderStyle.THIN) | |||
CellUtil.setCellStyleProperty(lastCpiCell, "borderBottom", BorderStyle.DOUBLE) | |||
@@ -606,7 +635,7 @@ open class ReportService( | |||
val row9: Row = sheet.getRow(rowNum) | |||
val cell5 = row9.createCell(2) | |||
cell5.apply { | |||
cellFormula = "N${lastRowNum}" | |||
cellFormula = "M${lastRowNum}" | |||
cellStyle.dataFormat = accountingStyle | |||
} | |||
@@ -691,12 +720,12 @@ open class ReportService( | |||
rowIndex = 10 | |||
sheet.getRow(rowIndex).apply { | |||
createCell(1).apply { | |||
setCellValue(project.expectedTotalFee!! * 0.8) | |||
setCellValue(if (project.expectedTotalFee != null) project.expectedTotalFee!! * 0.8 else 0.0) | |||
cellStyle.dataFormat = accountingStyle | |||
} | |||
createCell(2).apply { | |||
setCellValue(project.expectedTotalFee!!) | |||
setCellValue(project.expectedTotalFee ?: 0.0) | |||
cellStyle.dataFormat = accountingStyle | |||
} | |||
} | |||
@@ -979,10 +1008,15 @@ open class ReportService( | |||
project.milestones.forEach { milestone: Milestone -> | |||
val manHoursSpent = groupedTimesheets[Pair(project.id, milestone.id)]?.sum() ?: 0.0 | |||
val resourceUtilization = manHoursSpent / (milestone.stagePercentAllocation!! / 100 * project.totalManhour!!) | |||
val resourceUtilization = | |||
manHoursSpent / (milestone.stagePercentAllocation!! / 100 * project.totalManhour!!) | |||
// logger.info(project.name + " : " + milestone.taskGroup?.name + " : " + ChronoUnit.DAYS.between(LocalDate.now(), milestone.endDate)) | |||
// logger.info(daysUntilCurrentStageEnd) | |||
if (ChronoUnit.DAYS.between(LocalDate.now(), milestone.endDate) <= daysUntilCurrentStageEnd.toLong() && resourceUtilization <= resourceUtilizationPercentage.toDouble() / 100.0) { | |||
if (ChronoUnit.DAYS.between( | |||
LocalDate.now(), | |||
milestone.endDate | |||
) <= daysUntilCurrentStageEnd.toLong() && resourceUtilization <= resourceUtilizationPercentage.toDouble() / 100.0 | |||
) { | |||
milestoneCount++ | |||
val tempRow = sheet.getRow(rowIndex) ?: sheet.createRow(rowIndex) | |||
rowIndex++ | |||
@@ -1004,7 +1038,7 @@ open class ReportService( | |||
// | |||
// val resourceUtilization = | |||
// manHoursSpent / (milestone.stagePercentAllocation!! / 100 * project.totalManhour!!) | |||
setCellValue(resourceUtilization) | |||
setCellValue(resourceUtilization) | |||
// } else { | |||
// setCellValue(0.0) | |||
// } | |||
@@ -1080,6 +1114,13 @@ open class ReportService( | |||
val boldFont = workbook.createFont() | |||
boldFont.bold = true | |||
boldStyle.setFont(boldFont) | |||
val projectsStyle = workbook.createCellStyle() | |||
val projectsFont = workbook.createFont() | |||
projectsFont.bold = true | |||
projectsFont.fontName = "Times New Roman" | |||
projectsStyle.setFont(projectsFont) | |||
val daysOfMonth = (1..month.lengthOfMonth()).map { day -> | |||
val date = month.withDayOfMonth(day) | |||
val formattedDate = date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")) | |||
@@ -1228,7 +1269,7 @@ open class ReportService( | |||
projectList.forEachIndexed { index, title -> | |||
tempCell = sheet.getRow(7).createCell(columnIndex + index) | |||
tempCell.setCellValue(title) | |||
tempCell.cellStyle = boldStyle | |||
tempCell.cellStyle = projectsStyle | |||
CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER) | |||
CellUtil.setVerticalAlignment(tempCell, VerticalAlignment.CENTER) | |||
CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom) | |||
@@ -1271,7 +1312,7 @@ open class ReportService( | |||
///////////////////////////////////////////////////////// Leave Hours title //////////////////////////////////////////////////////////////////// | |||
tempCell = sheet.getRow(rowIndex).createCell(columnIndex) | |||
tempCell.setCellValue("Leave Hours") | |||
tempCell.cellStyle = boldStyle | |||
tempCell.cellStyle = projectsStyle | |||
CellUtil.setVerticalAlignment(tempCell, VerticalAlignment.CENTER) | |||
CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER) | |||
CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom) | |||
@@ -1279,8 +1320,10 @@ open class ReportService( | |||
columnIndex += 1 | |||
tempCell = sheet.getRow(rowIndex).createCell(columnIndex) | |||
tempCell.setCellValue("Daily Manhour Spent\n(Excluding Leave Hours)") | |||
tempCell.cellStyle = boldStyle | |||
CellUtil.setAlignment(tempCell, HorizontalAlignment.LEFT) | |||
tempCell.cellStyle = projectsStyle | |||
// CellUtil.setAlignment(tempCell, HorizontalAlignment.LEFT) | |||
CellUtil.setVerticalAlignment(tempCell, VerticalAlignment.CENTER) | |||
CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER) | |||
CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom) | |||
sheet.addMergedRegion(CellRangeAddress(6, 6, 2, columnIndex)) | |||
@@ -1391,7 +1434,7 @@ open class ReportService( | |||
rowIndex = 5 | |||
columnIndex = 0 | |||
generalCreateReportIndexed(sheet, result, rowIndex, columnIndex) | |||
generalCreateReportIndexed(sheet, result.distinct(), rowIndex, columnIndex) | |||
return workbook | |||
} | |||
@@ -1481,7 +1524,7 @@ open class ReportService( | |||
setDataAndConditionalFormatting(workbook, sheet, lateStartData, evaluator) | |||
// Automatically adjust column widths to fit content | |||
autoSizeColumns(sheet) | |||
autoSizeColumns(sheet) | |||
return workbook | |||
} | |||
@@ -1627,7 +1670,8 @@ open class ReportService( | |||
// NEW Column F: Subsidiary Name | |||
val subsidiaryNameCell = row.createCell(5) | |||
// subsidiaryNameCell.setCellValue(data["subsidiary_name"] as String) | |||
val subsidiaryName = data["subsidiary_name"] as? String ?: "N/A" // Checks if subsidiary_name is null and replaces it with "N/A" | |||
val subsidiaryName = | |||
data["subsidiary_name"] as? String ?: "N/A" // Checks if subsidiary_name is null and replaces it with "N/A" | |||
subsidiaryNameCell.setCellValue(subsidiaryName) | |||
// Column G: Project Plan Start Date | |||
@@ -1748,136 +1792,139 @@ open class ReportService( | |||
) | |||
return jdbcDao.queryForList(sql.toString(), args) | |||
} | |||
open fun getProjectCompletionReport(args: Map<String, Any>): List<Map<String, Any>> { | |||
val sql = StringBuilder("select" | |||
+ " result.code, " | |||
+ " result.name, " | |||
+ " result.teamCode, " | |||
+ " result.custCode, " | |||
+ " result.subCode, " ) | |||
val sql = StringBuilder( | |||
"select" | |||
+ " result.code, " | |||
+ " result.name, " | |||
+ " result.teamCode, " | |||
+ " result.custCode, " | |||
+ " result.subCode, " | |||
) | |||
if (args.get("outstanding") as Boolean) { | |||
sql.append(" result.projectFee - COALESCE(i.paidAmount, 0) as `Receivable Remained`, ") | |||
// sql.append(" result.projectFee - (result.totalBudget - COALESCE(i.issueAmount , 0) + COALESCE(i.issueAmount, 0) - COALESCE(i.paidAmount, 0)) as `Receivable Remained`, ") | |||
} | |||
sql.append( | |||
" DATE_FORMAT(result.actualEnd, '%d/%m/%Y') as actualEnd " | |||
+ " from ( " | |||
+ " SELECT " | |||
+ " pt.project_id, " | |||
+ " min(p.code) as code, " | |||
+ " min(p.name) as name, " | |||
+ " min(t.code) as teamCode, " | |||
+ " concat(min(c.code), ' - ', min(c.name)) as custCode, " | |||
+ " COALESCE(concat(min(ss.code),' - ',min(ss.name)), 'N/A') as subCode, " | |||
+ " min(p.actualEnd) as actualEnd, " | |||
+ " min(p.expectedTotalFee) as projectFee, " | |||
+ " sum(COALESCE(tns.totalConsumed*sal.hourlyRate, 0)) as totalBudget " | |||
+ " FROM ( " | |||
+ " SELECT " | |||
+ " t.staffId, " | |||
+ " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as totalConsumed, " | |||
+ " t.projectTaskId AS taskId " | |||
+ " FROM timesheet t " | |||
+ " LEFT JOIN staff s ON t.staffId = s.id " | |||
+ " LEFT JOIN team te on s.teamId = te.id " | |||
+ " GROUP BY t.staffId, t.projectTaskId " | |||
+ " order by t.staffId " | |||
+ " ) AS tns " | |||
+ " right join project_task pt ON tns.taskId = pt.id " | |||
+ " left join project p on p.id = pt.project_id " | |||
+ " left JOIN staff s ON p.teamLead = s.id " | |||
+ " left join salary sal on s.salaryId = sal.salaryPoint " | |||
+ " left JOIN team t ON s.teamId = t.id " | |||
+ " left join customer c on c.id = p.customerId " | |||
+ " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id " | |||
+ " where p.deleted = false ") | |||
if (args.containsKey("teamId")) { | |||
sql.append("t.id = :teamId") | |||
} | |||
sql.append( | |||
+ " from ( " | |||
+ " SELECT " | |||
+ " pt.project_id, " | |||
+ " min(p.code) as code, " | |||
+ " min(p.name) as name, " | |||
+ " min(t.code) as teamCode, " | |||
+ " concat(min(c.code), ' - ', min(c.name)) as custCode, " | |||
+ " COALESCE(concat(min(ss.code),' - ',min(ss.name)), 'N/A') as subCode, " | |||
+ " min(p.actualEnd) as actualEnd, " | |||
+ " min(p.expectedTotalFee) as projectFee, " | |||
+ " sum(COALESCE(tns.totalConsumed*sal.hourlyRate, 0)) as totalBudget " | |||
+ " FROM ( " | |||
+ " SELECT " | |||
+ " t.staffId, " | |||
+ " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as totalConsumed, " | |||
+ " t.projectTaskId AS taskId " | |||
+ " FROM timesheet t " | |||
+ " LEFT JOIN staff s ON t.staffId = s.id " | |||
+ " LEFT JOIN team te on s.teamId = te.id " | |||
+ " GROUP BY t.staffId, t.projectTaskId " | |||
+ " order by t.staffId " | |||
+ " ) AS tns " | |||
+ " right join project_task pt ON tns.taskId = pt.id " | |||
+ " left join project p on p.id = pt.project_id " | |||
+ " left JOIN staff s ON p.teamLead = s.id " | |||
+ " left join salary sal on s.salaryId = sal.salaryPoint " | |||
+ " left JOIN team t ON s.teamId = t.id " | |||
+ " left join customer c on c.id = p.customerId " | |||
+ " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id " | |||
+ " where p.deleted = false " | |||
) | |||
if (args.containsKey("teamId")) { | |||
sql.append("t.id = :teamId") | |||
} | |||
sql.append( | |||
" and p.status = 'Completed' " | |||
+ " and p.actualEnd BETWEEN :startDate and :endDate " | |||
+ " group by pt.project_id " | |||
+ " ) as result " | |||
+ " left join invoice i on result.code = i.projectCode " | |||
+ " order by result.actualEnd ") | |||
+ " and p.actualEnd BETWEEN :startDate and :endDate " | |||
+ " group by pt.project_id " | |||
+ " ) as result " | |||
+ " left join invoice i on result.code = i.projectCode " | |||
+ " order by result.actualEnd " | |||
) | |||
return jdbcDao.queryForList(sql.toString(), args) | |||
} | |||
open fun getProjectResourceOverconsumptionReport(args: Map<String, Any>): List<Map<String, Any>> { | |||
val sql = StringBuilder("WITH teamNormalConsumed AS (" | |||
+ " SELECT " | |||
+ " s.teamId, " | |||
+ " pt.project_id, " | |||
+ " SUM(tns.totalConsumed) AS totalConsumed, " | |||
+ " sum(tns.totalBudget) as totalBudget " | |||
+ " FROM ( " | |||
+ " SELECT " | |||
+ " t.staffId, " | |||
+ " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as totalConsumed, " | |||
+ " t.projectTaskId AS taskId, " | |||
+ " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) * min(sal.hourlyRate) as totalBudget " | |||
+ " FROM timesheet t " | |||
+ " LEFT JOIN staff s ON t.staffId = s.id " | |||
+ " left join salary sal on sal.salaryPoint = s.salaryId " | |||
+ " GROUP BY t.staffId, t.projectTaskId " | |||
+ " ) AS tns " | |||
+ " INNER JOIN project_task pt ON tns.taskId = pt.id " | |||
+ " JOIN staff s ON tns.staffId = s.id " | |||
+ " JOIN team t ON s.teamId = t.id " | |||
+ " GROUP BY teamId, project_id " | |||
+ " ) " | |||
+ " SELECT " | |||
+ " p.code, " | |||
+ " p.name, " | |||
+ " t.code as team, " | |||
+ " concat(c.code, ' - ', c.name) as client, " | |||
+ " COALESCE(concat(ss.code, ' - ', ss.name), 'N/A') as subsidiary, " | |||
+ " p.expectedTotalFee * 0.8 as plannedBudget, " | |||
+ " COALESCE(tns.totalBudget, 0) as actualConsumedBudget, " | |||
+ " COALESCE(p.totalManhour, 0) as plannedManhour, " | |||
+ " COALESCE(tns.totalConsumed, 0) as actualConsumedManhour, " | |||
+ " (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) as budgetConsumptionRate, " | |||
+ " (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) as manhourConsumptionRate, " | |||
+ " CASE " | |||
+ " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " | |||
+ " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1 " | |||
+ " then 'Potential Overconsumption' " | |||
+ " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " | |||
+ " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1 " | |||
+ " then 'Overconsumption' " | |||
+ " else 'Within Budget' " | |||
+ " END as status " | |||
+ " FROM project p " | |||
+ " LEFT JOIN team t ON p.teamLead = t.teamLead " | |||
+ " LEFT JOIN staff s ON p.teamLead = s.id " | |||
+ " LEFT JOIN salary sa ON s.salaryId = sa.salaryPoint " | |||
+ " LEFT JOIN customer c ON p.customerId = c.id " | |||
+ " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id " | |||
+ " left join teamNormalConsumed tns on tns.project_id = p.id " | |||
+ " WHERE p.deleted = false " | |||
+ " and p.status = 'On-going' " | |||
val sql = StringBuilder( | |||
"WITH teamNormalConsumed AS (" | |||
+ " SELECT" | |||
+ " tns.project_id," | |||
+ " SUM(tns.totalConsumed) AS totalConsumed, " | |||
+ " sum(tns.totalBudget) as totalBudget " | |||
+ " FROM ( " | |||
+ " SELECT" | |||
+ " t.staffId," | |||
+ " t.projectId AS project_id," | |||
+ " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as totalConsumed, " | |||
+ " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) * min(sal.hourlyRate) as totalBudget " | |||
+ " FROM timesheet t" | |||
+ " LEFT JOIN staff s ON t.staffId = s.id " | |||
+ " left join salary sal on sal.salaryPoint = s.salaryId " | |||
+ " GROUP BY t.staffId, t.projectId" | |||
+ " ) AS tns" | |||
+ " GROUP BY project_id" | |||
+ " ) " | |||
+ " SELECT " | |||
+ " p.code, " | |||
+ " p.name, " | |||
+ " t.code as team, " | |||
+ " concat(c.code, ' - ', c.name) as client, " | |||
+ " COALESCE(concat(ss.code, ' - ', ss.name), 'N/A') as subsidiary, " | |||
+ " p.expectedTotalFee * 0.8 as plannedBudget, " | |||
+ " COALESCE(tns.totalBudget, 0) as actualConsumedBudget, " | |||
+ " COALESCE(p.totalManhour, 0) as plannedManhour, " | |||
+ " COALESCE(tns.totalConsumed, 0) as actualConsumedManhour, " | |||
+ " (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) as budgetConsumptionRate, " | |||
+ " (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) as manhourConsumptionRate, " | |||
+ " CASE " | |||
+ " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " | |||
+ " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1 " | |||
+ " then 'Overconsumption' " | |||
+ " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " | |||
+ " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1 " | |||
+ " then 'Potential Overconsumption' " | |||
+ " else 'Within Budget' " | |||
+ " END as status " | |||
+ " FROM project p " | |||
+ " LEFT JOIN team t ON p.teamLead = t.teamLead " | |||
+ " LEFT JOIN staff s ON p.teamLead = s.id " | |||
+ " LEFT JOIN salary sa ON s.salaryId = sa.salaryPoint " | |||
+ " LEFT JOIN customer c ON p.customerId = c.id " | |||
+ " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id " | |||
+ " left join teamNormalConsumed tns on tns.project_id = p.id " | |||
+ " WHERE p.deleted = false " | |||
+ " and p.status = 'On-going' " | |||
) | |||
if (args != null) { | |||
var statusFilter: String = "" | |||
if (args.containsKey("teamId")) | |||
sql.append("and t.id = :teamId") | |||
sql.append(" and t.id = :teamId") | |||
if (args.containsKey("custId")) | |||
sql.append("and c.id = :custId") | |||
sql.append(" and c.id = :custId") | |||
if (args.containsKey("subsidiaryId")) | |||
sql.append("and ss.id = :subsidiaryId") | |||
sql.append(" and ss.id = :subsidiaryId") | |||
if (args.containsKey("status")) | |||
statusFilter = when (args.get("status")) { | |||
"Potential Overconsumption" -> "and " + | |||
"Potential Overconsumption" -> " and " + | |||
" (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit " + | |||
" and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " + | |||
" or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit " + | |||
" and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1 " | |||
"All" -> "and " + | |||
"All" -> " and " + | |||
" (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit " + | |||
" or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit " | |||
// "Overconsumption" -> "and " + | |||
// "Overconsumption" -> " and " + | |||
// " ((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " + | |||
// " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1) " | |||
@@ -1900,6 +1947,15 @@ open class ReportService( | |||
+ " left join salary s2 on s.salaryId = s2.salaryPoint" | |||
+ " left join team t2 on t2.id = s.teamId" | |||
+ " )," | |||
+ " cte_timesheet_sum as (" | |||
+ " Select p.code, sum((IFNULL(t.normalConsumed, 0) + IFNULL(t.otConsumed , 0)) * s2.hourlyRate) as sumManhourExpenditure" | |||
+ " from timesheet t" | |||
+ " left join project_task pt on pt.id = t.projectTaskId" | |||
+ " left join project p ON p.id = pt.project_id" | |||
+ " left join staff s on s.id = t.staffId" | |||
+ " left join salary s2 on s.salaryId = s2.salaryPoint" | |||
+ " group by p.code" | |||
+ " )," | |||
+ " cte_invoice as (" | |||
+ " select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount" | |||
+ " from invoice i" | |||
@@ -1909,10 +1965,11 @@ open class ReportService( | |||
+ " select p.code, p.description, c.name as client, IFNULL(s2.name, \"N/A\") as subsidiary, concat(t.code, \" - \", t.name) as teamLead," | |||
+ " IFNULL(cte_ts.normalConsumed, 0) as normalConsumed, IFNULL(cte_ts.otConsumed, 0) as otConsumed, DATE_FORMAT(cte_ts.recordDate, '%Y-%m') as recordDate, " | |||
+ " IFNULL(cte_ts.salaryPoint, 0) as salaryPoint, " | |||
+ " IFNULL(cte_ts.hourlyRate, 0) as hourlyRate, IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount, IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount," | |||
+ " IFNULL(cte_ts.hourlyRate, 0) as hourlyRate, IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount, IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount, IFNULL(cte_tss.sumManhourExpenditure, 0) as sumManhourExpenditure," | |||
+ " s.name, s.staffId, g.code as gradeCode, g.name as gradeName, t2.code as teamCode, t2.name as teamName" | |||
+ " from project p" | |||
+ " left join cte_timesheet cte_ts on p.code = cte_ts.code" | |||
+ " left join cte_timesheet_sum cte_tss on p.code = cte_tss.code" | |||
+ " left join customer c on c.id = p.customerId" | |||
+ " left join tsmsdb.team t on t.teamLead = p.teamLead" | |||
+ " left join cte_invoice cte_i on cte_i.code = p.code" | |||
@@ -1975,6 +2032,9 @@ open class ReportService( | |||
if (info["code"] == item["code"] && "subsidiary" !in info) { | |||
info["subsidiary"] = item.getValue("subsidiary") | |||
} | |||
if (info["manhourExpenditure"] != item.getValue("sumManhourExpenditure")) { | |||
info["manhourExpenditure"] = item.getValue("sumManhourExpenditure") | |||
} | |||
if (info["description"] != item.getValue("description")) { | |||
info["description"] = item.getValue("description") | |||
} | |||
@@ -2162,9 +2222,9 @@ open class ReportService( | |||
rowNum = 6 | |||
val row6: Row = sheet.getRow(rowNum) | |||
val row6Cell = row6.getCell(1) | |||
val clientSubsidiary = if(info.getValue("subsidiary").toString() != "N/A"){ | |||
val clientSubsidiary = if (info.getValue("subsidiary").toString() != "N/A") { | |||
info.getValue("subsidiary").toString() | |||
}else{ | |||
} else { | |||
info.getValue("client").toString() | |||
} | |||
row6Cell.setCellValue(clientSubsidiary) | |||
@@ -2335,7 +2395,8 @@ open class ReportService( | |||
} | |||
val totalManhourECell = totalManhourERow.getCell(1) ?: totalManhourERow.createCell(1) | |||
totalManhourECell.apply { | |||
cellFormula = "SUM(${lastColumnIndex}${startRow}:${lastColumnIndex}${startRow + staffInfoList.size})" | |||
// cellFormula = "SUM(${lastColumnIndex}${startRow}:${lastColumnIndex}${startRow + staffInfoList.size})" | |||
setCellValue(info.getValue("manhourExpenditure") as Double) | |||
cellStyle.dataFormat = accountingStyle | |||
} | |||
CellUtil.setCellStyleProperty(totalManhourETitleCell, "borderBottom", BorderStyle.THIN) | |||
@@ -2358,10 +2419,10 @@ open class ReportService( | |||
return workbook | |||
} | |||
fun getCostAndExpense(clientId: Long?, teamId: Long?, type: String): List<Map<String,Any?>>{ | |||
fun getCostAndExpense(clientId: Long?, teamId: Long?, type: String): List<Map<String, Any?>> { | |||
val sql = StringBuilder( | |||
" with cte_timesheet as ( " | |||
+ " Select p.code, s.name as staff, IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.salaryPoint, s2.hourlyRate, t.staffId," | |||
+ " Select p.code, s.name as staff, IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.salaryPoint, s2.hourlyRate, t.staffId," | |||
+ " t.recordDate" | |||
+ " from timesheet t" | |||
+ " left join project_task pt on pt.id = t.projectTaskId" | |||
@@ -2385,20 +2446,20 @@ open class ReportService( | |||
+ " left join team t2 on t2.id = s.teamId" | |||
+ " where ISNULL(p.code) = False" | |||
) | |||
if(clientId != null){ | |||
if(type == "client"){ | |||
if (clientId != null) { | |||
if (type == "client") { | |||
sql.append( | |||
" and c.id = :clientId " | |||
) | |||
} | |||
if(type == "subsidiary"){ | |||
if (type == "subsidiary") { | |||
sql.append( | |||
" and s2.id = :clientId " | |||
) | |||
} | |||
} | |||
if(teamId != null){ | |||
if (teamId != null) { | |||
sql.append( | |||
" and p.teamLead = :teamId " | |||
) | |||
@@ -2417,9 +2478,9 @@ open class ReportService( | |||
val queryList = jdbcDao.queryForList(sql.toString(), args) | |||
val costAndExpenseList = mutableListOf<Map<String, Any?>>() | |||
for(item in queryList){ | |||
for (item in queryList) { | |||
val hourlyRate = (item.getValue("hourlyRate") as BigDecimal).toDouble() | |||
if(item["code"] !in costAndExpenseList){ | |||
if (item["code"] !in costAndExpenseList.map { it["code"] }) { | |||
costAndExpenseList.add( | |||
mapOf( | |||
"code" to item["code"], | |||
@@ -2428,22 +2489,27 @@ open class ReportService( | |||
"subsidiary" to item["subsidiary"], | |||
"teamLead" to item["teamLead"], | |||
"budget" to item["expectedTotalFee"], | |||
"totalManhours" to item["normalConsumed"] as Double + item["otConsumed"] as Double, | |||
"manhourExpenditure" to (hourlyRate * item["normalConsumed"] as Double ) | |||
+ (hourlyRate * item["otConsumed"]as Double * otFactor) | |||
"totalManhours" to item["normalConsumed"] as Double + item["otConsumed"] as Double, | |||
"manhourExpenditure" to (hourlyRate * item["normalConsumed"] as Double) | |||
+ (hourlyRate * item["otConsumed"] as Double * otFactor) | |||
) | |||
) | |||
}else{ | |||
} else { | |||
val existingMap = costAndExpenseList.find { it.containsValue(item["code"]) }!! | |||
costAndExpenseList[costAndExpenseList.indexOf(existingMap)] = existingMap.toMutableMap().apply { | |||
put("totalManhours", get("manhours") as Double + (item["normalConsumed"] as Double + item["otConsumed"] as Double)) | |||
put("manhourExpenditure", get("manhourExpenditure") as Double + ((hourlyRate * item["normalConsumed"] as Double ) | |||
+ (hourlyRate * item["otConsumed"]as Double * otFactor))) | |||
put( | |||
"totalManhours", | |||
get("totalManhours") as Double + (item["normalConsumed"] as Double + item["otConsumed"] as Double) | |||
) | |||
put( | |||
"manhourExpenditure", | |||
get("manhourExpenditure") as Double + ((hourlyRate * item["normalConsumed"] as Double) | |||
+ (hourlyRate * item["otConsumed"] as Double * otFactor)) | |||
) | |||
} | |||
} | |||
} | |||
val result = costAndExpenseList.map { | |||
item -> | |||
val result = costAndExpenseList.map { item -> | |||
val budget = (item["budget"] as? Double)?.times(0.8) ?: 0.0 | |||
val budgetRemain = budget - (item["manhourExpenditure"] as? Double ?: 0.0) | |||
val remainingPercent = (budgetRemain / budget) | |||
@@ -2462,7 +2528,7 @@ open class ReportService( | |||
clientId: Long?, | |||
budgetPercentage: Double?, | |||
type: String | |||
): Workbook{ | |||
): Workbook { | |||
val resource = ClassPathResource(templatePath) | |||
val templateInputStream = resource.inputStream | |||
val workbook: Workbook = XSSFWorkbook(templateInputStream) | |||
@@ -2480,9 +2546,9 @@ open class ReportService( | |||
rowNum = 2 | |||
val row2: Row = sheet.getRow(rowNum) | |||
val row2Cell = row2.getCell(2) | |||
if(teamId == null){ | |||
if (teamId == null) { | |||
row2Cell.setCellValue("All") | |||
}else{ | |||
} else { | |||
val sql = StringBuilder( | |||
" select t.id, t.code, t.name, concat(t.code, \" - \" ,t.name) as teamLead from team t where t.id = :teamId " | |||
) | |||
@@ -2493,16 +2559,16 @@ open class ReportService( | |||
rowNum = 3 | |||
val row3: Row = sheet.getRow(rowNum) | |||
val row3Cell = row3.getCell(2) | |||
if(clientId == null){ | |||
if (clientId == null) { | |||
row3Cell.setCellValue("All") | |||
}else{ | |||
val sql= StringBuilder() | |||
if(type == "client"){ | |||
} else { | |||
val sql = StringBuilder() | |||
if (type == "client") { | |||
sql.append( | |||
" select c.id, c.name from customer c where c.id = :clientId " | |||
) | |||
} | |||
if(type == "subsidiary"){ | |||
if (type == "subsidiary") { | |||
sql.append( | |||
" select s.id, s.name from subsidiary s where s.id = :clientId " | |||
) | |||
@@ -2513,15 +2579,15 @@ open class ReportService( | |||
val filterList: List<Map<String, Any?>> | |||
if(budgetPercentage != null){ | |||
if (budgetPercentage != null) { | |||
filterList = costAndExpenseList.filter { ((it["budgetPercentage"] as? Double) ?: 0.0) <= budgetPercentage } | |||
}else{ | |||
} else { | |||
filterList = costAndExpenseList | |||
} | |||
rowNum = 6 | |||
for(item in filterList){ | |||
for (item in filterList) { | |||
val index = filterList.indexOf(item) | |||
val row: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) | |||
val cell = row.getCell(0) ?: row.createCell(0) | |||
@@ -2571,13 +2637,13 @@ open class ReportService( | |||
val cell8 = row.getCell(8) ?: row.createCell(8) | |||
cell8.apply { | |||
cellFormula = "G${rowNum+1}-H${rowNum+1}" | |||
cellFormula = "G${rowNum + 1}-H${rowNum + 1}" | |||
} | |||
CellUtil.setCellStyleProperty(cell8, "dataFormat", accountingStyle) | |||
val cell9 = row.getCell(9) ?: row.createCell(9) | |||
cell9.apply { | |||
cellFormula = "I${rowNum+1}/G${rowNum+1}" | |||
cellFormula = "I${rowNum + 1}/G${rowNum + 1}" | |||
} | |||
CellUtil.setCellStyleProperty(cell9, "dataFormat", percentStyle) | |||
@@ -2587,11 +2653,18 @@ open class ReportService( | |||
return workbook | |||
} | |||
fun genCostAndExpenseReport(request: costAndExpenseRequest): ByteArray{ | |||
fun genCostAndExpenseReport(request: costAndExpenseRequest): ByteArray { | |||
val costAndExpenseList = getCostAndExpense(request.clientId, request.teamId, request.type) | |||
val workbook: Workbook = createCostAndExpenseWorkbook(COSTANDEXPENSE_REPORT, costAndExpenseList, request.teamId, request.clientId, request.budgetPercentage, request.type) | |||
val workbook: Workbook = createCostAndExpenseWorkbook( | |||
COSTANDEXPENSE_REPORT, | |||
costAndExpenseList, | |||
request.teamId, | |||
request.clientId, | |||
request.budgetPercentage, | |||
request.type | |||
) | |||
val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() | |||
workbook.write(outputStream) | |||
@@ -2599,8 +2672,16 @@ open class ReportService( | |||
return outputStream.toByteArray() | |||
} | |||
open fun getLateStartDetails(teamId: Long?, clientId: Long?, remainedDate: LocalDate, remainedDateTo: LocalDate, type: String?): List<Map<String, Any>> { | |||
val sql = StringBuilder(""" | |||
open fun getLateStartDetails( | |||
teamId: Long?, | |||
clientId: Long?, | |||
remainedDate: LocalDate, | |||
remainedDateTo: LocalDate, | |||
type: String? | |||
): List<Map<String, Any>> { | |||
val sql = StringBuilder( | |||
""" | |||
SELECT | |||
p.code AS project_code, | |||
p.name AS project_name, | |||
@@ -2621,7 +2702,8 @@ open class ReportService( | |||
p.status = 'Pending to Start' | |||
AND p.planStart < CURRENT_DATE | |||
AND m.endDate BETWEEN :remainedDate AND :remainedDateTo | |||
""".trimIndent()) | |||
""".trimIndent() | |||
) | |||
if (teamId != null && teamId > 0) { | |||
sql.append(" AND t.id = :teamId") | |||
@@ -2645,4 +2727,240 @@ open class ReportService( | |||
) | |||
return jdbcDao.queryForList(sql.toString(), args) | |||
} | |||
} | |||
@Throws(IOException::class) | |||
private fun createCrossTeamChargeReport( | |||
month: String, | |||
timesheets: List<Timesheet>, | |||
teams: List<Team>, | |||
grades: List<Grade>, | |||
templatePath: String, | |||
): Workbook { | |||
// please create a new function for each report template | |||
val resource = ClassPathResource(templatePath) | |||
val templateInputStream = resource.inputStream | |||
val workbook: Workbook = XSSFWorkbook(templateInputStream) | |||
val sheet: Sheet = workbook.getSheetAt(0) | |||
// accounting style + comma style | |||
val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") | |||
// bold font with border style | |||
val boldFont = workbook.createFont().apply { | |||
bold = true | |||
fontName = "Times New Roman" | |||
} | |||
val boldFontWithBorderStyle = workbook.createCellStyle().apply { | |||
setFont(boldFont) | |||
borderTop = BorderStyle.THIN | |||
borderBottom = BorderStyle.THIN | |||
borderLeft = BorderStyle.THIN | |||
borderRight = BorderStyle.THIN | |||
} | |||
// normal font | |||
val normalFont = workbook.createFont().apply { | |||
fontName = "Times New Roman" | |||
} | |||
val normalFontWithBorderStyle = workbook.createCellStyle().apply { | |||
setFont(normalFont) | |||
borderTop = BorderStyle.THIN | |||
borderBottom = BorderStyle.THIN | |||
borderLeft = BorderStyle.THIN | |||
borderRight = BorderStyle.THIN | |||
} | |||
var rowIndex = 1 // Assuming the location is in (1,2), which is the report date field | |||
var columnIndex = 2 | |||
val monthFormat = DateTimeFormatter.ofPattern("MMMM yyyy", Locale.ENGLISH) | |||
val reportMonth = YearMonth.parse(month, DateTimeFormatter.ofPattern("yyyy-MM")) | |||
val convertReportMonth = YearMonth.of(reportMonth.year, reportMonth.month).format(monthFormat) | |||
sheet.getRow(rowIndex).createCell(columnIndex).apply { | |||
setCellValue(convertReportMonth) | |||
} | |||
// if (timesheets.isNotEmpty()) { | |||
val combinedTeamCodeColNumber = grades.size | |||
val sortedGrades = grades.sortedBy { it.id } | |||
val sortedTeams = teams.sortedBy { it.id } | |||
val groupedTimesheets = timesheets | |||
.filter { it.project?.teamLead?.team?.id != it.staff?.team?.id } | |||
.groupBy { timesheetEntry -> | |||
Triple( | |||
timesheetEntry.project?.teamLead?.team?.id, | |||
timesheetEntry.staff?.team?.id, | |||
timesheetEntry.staff?.grade?.id | |||
) | |||
} | |||
.mapValues { (_, timesheetEntries) -> | |||
timesheetEntries.map { timesheet -> | |||
if (timesheet.normalConsumed != null) { | |||
mutableMapOf<String, Double>().apply { | |||
this["manHour"] = timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) | |||
this["salary"] = timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) | |||
.times(timesheet.staff!!.salary.hourlyRate.toDouble()) | |||
} | |||
} else if (timesheet.otConsumed != null) { | |||
mutableMapOf<String, Double>().apply { | |||
this["manHour"] = timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) | |||
this["salary"] = timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) | |||
.times(timesheet.staff!!.salary.hourlyRate.toDouble()) | |||
} | |||
} else { | |||
mutableMapOf<String, Double>().apply { | |||
this["manHour"] = 0.0 | |||
this["salary"] = 0.0 | |||
} | |||
} | |||
} | |||
} | |||
if (sortedTeams.isNotEmpty() && sortedTeams.size > 1) { | |||
rowIndex = 3 | |||
sortedTeams.forEach { team: Team -> | |||
// Team | |||
sheet.createRow(rowIndex++).apply { | |||
createCell(0).apply { | |||
setCellValue("Team to be charged:") | |||
cellStyle = boldFontWithBorderStyle | |||
CellUtil.setAlignment(this, HorizontalAlignment.LEFT) | |||
} | |||
val rangeAddress = CellRangeAddress(this.rowNum, this.rowNum, 1, 2 + combinedTeamCodeColNumber) | |||
sheet.addMergedRegion(rangeAddress) | |||
RegionUtil.setBorderTop(BorderStyle.THIN, rangeAddress, sheet) | |||
RegionUtil.setBorderLeft(BorderStyle.THIN, rangeAddress, sheet) | |||
RegionUtil.setBorderRight(BorderStyle.THIN, rangeAddress, sheet) | |||
RegionUtil.setBorderBottom(BorderStyle.THIN, rangeAddress, sheet) | |||
createCell(1).apply { | |||
setCellValue(team.code) | |||
cellStyle = normalFontWithBorderStyle | |||
CellUtil.setAlignment(this, HorizontalAlignment.CENTER) | |||
} | |||
} | |||
// Grades | |||
sheet.createRow(rowIndex++).apply { | |||
columnIndex = 0 | |||
createCell(columnIndex++).apply { | |||
setCellValue("") | |||
cellStyle = boldFontWithBorderStyle | |||
} | |||
sortedGrades.forEach { grade: Grade -> | |||
createCell(columnIndex++).apply { | |||
setCellValue(grade.name) | |||
cellStyle = boldFontWithBorderStyle | |||
CellUtil.setAlignment(this, HorizontalAlignment.CENTER) | |||
} | |||
} | |||
createCell(columnIndex++).apply { | |||
setCellValue("Total Manhour by Team") | |||
cellStyle = boldFontWithBorderStyle | |||
} | |||
createCell(columnIndex).apply { | |||
setCellValue("Total Cost Adjusted by Salary Point by Team") | |||
cellStyle = boldFontWithBorderStyle | |||
} | |||
} | |||
// Team + Manhour | |||
val startRow = rowIndex | |||
var endRow = rowIndex | |||
sortedTeams.forEach { chargedTeam: Team -> | |||
if (team.id != chargedTeam.id) { | |||
endRow++ | |||
sheet.createRow(rowIndex++).apply { | |||
columnIndex = 0 | |||
createCell(columnIndex++).apply { | |||
setCellValue(chargedTeam.code) | |||
cellStyle = normalFontWithBorderStyle | |||
CellUtil.setAlignment(this, HorizontalAlignment.CENTER) | |||
} | |||
var totalSalary = 0.0 | |||
sortedGrades.forEach { grade: Grade -> | |||
createCell(columnIndex++).apply { | |||
setCellValue( | |||
groupedTimesheets[Triple( | |||
team.id, | |||
chargedTeam.id, | |||
grade.id | |||
)]?.sumOf { it.getValue("manHour") } ?: 0.0) | |||
totalSalary += groupedTimesheets[Triple( | |||
team.id, | |||
chargedTeam.id, | |||
grade.id | |||
)]?.sumOf { it.getValue("salary") } ?: 0.0 | |||
cellStyle = normalFontWithBorderStyle.apply { | |||
dataFormat = accountingStyle | |||
} | |||
} | |||
} | |||
createCell(columnIndex++).apply { | |||
val lastCellLetter = CellReference.convertNumToColString(this.columnIndex - 1) | |||
cellFormula = "sum(B${this.rowIndex + 1}:${lastCellLetter}${this.rowIndex + 1})" | |||
cellStyle = boldFontWithBorderStyle.apply { | |||
dataFormat = accountingStyle | |||
} | |||
} | |||
createCell(columnIndex).apply { | |||
setCellValue(totalSalary) | |||
cellStyle = boldFontWithBorderStyle.apply { | |||
dataFormat = accountingStyle | |||
} | |||
} | |||
} | |||
} | |||
} | |||
// Total Manhour by grade | |||
sheet.createRow(rowIndex).apply { | |||
columnIndex = 0 | |||
createCell(columnIndex++).apply { | |||
setCellValue("Total Manhour by Grade") | |||
cellStyle = boldFontWithBorderStyle | |||
} | |||
sortedGrades.forEach { grade: Grade -> | |||
createCell(columnIndex++).apply { | |||
val currentCellLetter = CellReference.convertNumToColString(this.columnIndex) | |||
cellFormula = "sum(${currentCellLetter}${startRow}:${currentCellLetter}${endRow})" | |||
cellStyle = normalFontWithBorderStyle.apply { | |||
dataFormat = accountingStyle | |||
} | |||
} | |||
} | |||
createCell(columnIndex++).apply { | |||
setCellValue("") | |||
cellStyle = boldFontWithBorderStyle | |||
} | |||
createCell(columnIndex).apply { | |||
setCellValue("") | |||
cellStyle = boldFontWithBorderStyle | |||
} | |||
} | |||
rowIndex += 2 | |||
} | |||
} | |||
// } | |||
return workbook | |||
} | |||
} |
@@ -34,8 +34,12 @@ import java.time.format.DateTimeFormatter | |||
import com.ffii.tsms.modules.project.entity.Project | |||
import com.ffii.tsms.modules.project.service.SubsidiaryService | |||
import com.ffii.tsms.modules.report.web.model.* | |||
import org.apache.commons.logging.Log | |||
import org.apache.commons.logging.LogFactory | |||
import org.springframework.data.domain.Example | |||
import org.springframework.data.domain.ExampleMatcher | |||
import java.time.YearMonth | |||
import java.util.* | |||
@RestController | |||
@RequestMapping("/reports") | |||
@@ -55,9 +59,11 @@ class ReportController( | |||
private val customerService: CustomerService, | |||
private val subsidiaryService: SubsidiaryService, | |||
private val invoiceService: InvoiceService, private val gradeAllocationRepository: GradeAllocationRepository, | |||
private val subsidiaryRepository: SubsidiaryRepository | |||
private val subsidiaryRepository: SubsidiaryRepository, private val staffAllocationRepository: StaffAllocationRepository, | |||
private val gradeRepository: GradeRepository | |||
) { | |||
private val logger: Log = LogFactory.getLog(javaClass) | |||
@PostMapping("/fetchProjectsFinancialStatusReport") | |||
@Throws(ServletRequestBindingException::class, IOException::class) | |||
fun getFinancialStatusReport(@RequestBody @Valid request: FinancialStatusReportRequest): ResponseEntity<Resource> { | |||
@@ -76,7 +82,8 @@ class ReportController( | |||
val project = projectRepository.findById(request.projectId).orElseThrow() | |||
val projectTasks = projectTaskRepository.findAllByProject(project) | |||
val invoices = invoiceService.findAllByProjectAndPaidAmountIsNotNull(project) | |||
// val invoices = invoiceService.findAllByProjectAndPaidAmountIsNotNull(project) | |||
val invoices = invoiceRepository.findAllByProjectCodeAndPaidAmountIsNotNull(project.code!!) | |||
val timesheets = timesheetRepository.findAllByProjectTaskIn(projectTasks) | |||
val reportResult: ByteArray = excelReportService.generateProjectCashFlowReport(project, invoices, timesheets, request.dateType) | |||
@@ -300,4 +307,35 @@ class ReportController( | |||
.body(ByteArrayResource(reportResult)) | |||
} | |||
@PostMapping("/CrossTeamChargeReport") | |||
@Throws(ServletRequestBindingException::class, IOException::class) | |||
fun getCrossTeamChargeReport(@RequestBody @Valid request: CrossTeamChargeReportRequest): ResponseEntity<Resource> { | |||
val authorities = staffsService.currentAuthorities() ?: return ResponseEntity.noContent().build() | |||
if (authorities.stream().anyMatch { it.authority.equals("GENERATE_REPORTS") }) { | |||
val crossProjects = staffAllocationRepository.findAll() | |||
.filter { it.project?.teamLead?.team?.id != it.staff?.team?.id } | |||
.map { it.project!! } | |||
.distinct() | |||
val reportMonth = YearMonth.parse(request.month, DateTimeFormatter.ofPattern("yyyy-MM")) | |||
val convertReportMonth = YearMonth.of(reportMonth.year, reportMonth.month) | |||
val startDate = convertReportMonth.atDay(1) | |||
val endDate = convertReportMonth.atEndOfMonth() | |||
val timesheets = timesheetRepository.findAllByProjectIn(crossProjects) | |||
.filter { it.recordDate != null && | |||
(it.recordDate!!.isEqual(startDate) || it.recordDate!!.isEqual(endDate) || (it.recordDate!!.isAfter(startDate) && it.recordDate!!.isBefore(endDate)))} | |||
val teams = teamRepository.findAll().filter { it.deleted == false } | |||
val grades = gradeRepository.findAll().filter { it.deleted == false } | |||
val reportResult: ByteArray = excelReportService.generateCrossTeamChargeReport(request.month, timesheets, teams, grades) | |||
return ResponseEntity.ok() | |||
.header("filename", "Cross Team Charge Report - " + LocalDate.now() + ".xlsx") | |||
.body(ByteArrayResource(reportResult)) | |||
} else { | |||
return ResponseEntity.noContent().build() | |||
} | |||
} | |||
} |
@@ -57,4 +57,8 @@ data class ProjectCompletionReport ( | |||
val startDate: LocalDate, | |||
val endDate: LocalDate, | |||
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 findAllByProjectIn(project: List<Project>): List<Timesheet> | |||
fun deleteAllByStaffAndRecordDate(staff: Staff, recordDate: LocalDate) | |||
@Query("SELECT new com.ffii.tsms.modules.timesheet.entity.projections.TimesheetHours(IFNULL(SUM(normalConsumed), 0), IFNULL(SUM(otConsumed), 0)) FROM Timesheet t JOIN ProjectTask pt on t.projectTask = pt WHERE pt.project = ?1") | |||
@@ -1,17 +1,22 @@ | |||
package com.ffii.tsms.modules.timesheet.service | |||
import com.ffii.core.exception.BadRequestException | |||
import com.ffii.core.utils.ExcelUtils | |||
import com.ffii.tsms.modules.data.entity.BuildingType | |||
import com.ffii.tsms.modules.data.entity.Staff | |||
import com.ffii.tsms.modules.data.entity.StaffRepository | |||
import com.ffii.tsms.modules.data.entity.WorkNature | |||
import com.ffii.tsms.modules.data.service.StaffsService | |||
import com.ffii.tsms.modules.data.service.TeamService | |||
import com.ffii.tsms.modules.project.entity.ProjectRepository | |||
import com.ffii.tsms.modules.project.entity.ProjectTaskRepository | |||
import com.ffii.tsms.modules.project.entity.TaskRepository | |||
import com.ffii.tsms.modules.project.entity.* | |||
import com.ffii.tsms.modules.project.web.models.* | |||
import com.ffii.tsms.modules.timesheet.entity.Timesheet | |||
import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository | |||
import com.ffii.tsms.modules.timesheet.web.models.TeamMemberTimeEntries | |||
import com.ffii.tsms.modules.timesheet.web.models.TimeEntry | |||
import org.apache.commons.logging.LogFactory | |||
import org.apache.poi.ss.usermodel.Sheet | |||
import org.apache.poi.ss.usermodel.Workbook | |||
import org.springframework.stereotype.Service | |||
import org.springframework.transaction.annotation.Transactional | |||
import java.time.LocalDate | |||
@@ -40,14 +45,15 @@ open class TimesheetsService( | |||
mergeTimeEntriesByProjectAndTask(timeEntries).map { timeEntry -> | |||
val task = timeEntry.taskId?.let { taskRepository.findById(it).getOrNull() } | |||
val project = timeEntry.projectId?.let { projectRepository.findById(it).getOrNull() } | |||
val projectTask = project?.let { p -> task?.let { t -> projectTaskRepository.findByProjectAndTask(p, t) } } | |||
val projectTask = | |||
project?.let { p -> task?.let { t -> projectTaskRepository.findByProjectAndTask(p, t) } } | |||
Timesheet().apply { | |||
this.staff = currentStaff | |||
this.recordDate = entryDate | |||
this.normalConsumed = timeEntry.inputHours | |||
this.otConsumed = timeEntry.otHours | |||
this.projectTask = projectTask | |||
this.projectTask = projectTask | |||
this.project = project | |||
this.remark = timeEntry.remark | |||
} | |||
@@ -60,7 +66,11 @@ open class TimesheetsService( | |||
} | |||
@Transactional | |||
open fun saveMemberTimeEntry(staffId: Long, entry: TimeEntry, recordDate: LocalDate?): Map<String, List<TimeEntry>> { | |||
open fun saveMemberTimeEntry( | |||
staffId: Long, | |||
entry: TimeEntry, | |||
recordDate: LocalDate? | |||
): Map<String, List<TimeEntry>> { | |||
val authorities = staffsService.currentAuthorities() ?: throw BadRequestException() | |||
if (!authorities.stream().anyMatch { it.authority.equals("MAINTAIN_TIMESHEET") }) { | |||
@@ -75,11 +85,11 @@ open class TimesheetsService( | |||
val timesheet = timesheetRepository.findById(entry.id).getOrDefault(Timesheet()).apply { | |||
val task = entry.taskId?.let { taskRepository.findById(it).getOrNull() } | |||
val project = entry.projectId?.let { projectRepository.findById(it).getOrNull() } | |||
val projectTask = project?.let { p -> task?.let { t -> projectTaskRepository.findByProjectAndTask(p, t) } } | |||
val projectTask = project?.let { p -> task?.let { t -> projectTaskRepository.findByProjectAndTask(p, t) } } | |||
this.normalConsumed = entry.inputHours | |||
this.otConsumed = entry.otHours | |||
this.projectTask = projectTask | |||
this.projectTask = projectTask | |||
this.project = project | |||
this.remark = entry.remark | |||
this.recordDate = this.recordDate ?: recordDate | |||
@@ -109,7 +119,7 @@ open class TimesheetsService( | |||
open fun getTeamMemberTimesheet(): Map<Long, TeamMemberTimeEntries> { | |||
val authorities = staffsService.currentAuthorities() ?: return emptyMap() | |||
if (authorities.stream().anyMatch { it.authority.equals("MAINTAIN_TIMESHEET")}) { | |||
if (authorities.stream().anyMatch { it.authority.equals("MAINTAIN_TIMESHEET") }) { | |||
val currentStaff = staffsService.currentStaff() | |||
// Get team where current staff is team lead | |||
@@ -136,27 +146,118 @@ open class TimesheetsService( | |||
private fun transformToTimeEntryMap(timesheets: List<Timesheet>): Map<String, List<TimeEntry>> { | |||
return timesheets | |||
.groupBy { timesheet -> timesheet.recordDate!!.format(DateTimeFormatter.ISO_LOCAL_DATE) } | |||
.mapValues { (_, timesheets) -> timesheets.map { timesheet -> | |||
TimeEntry( | |||
id = timesheet.id!!, | |||
projectId = timesheet.projectTask?.project?.id ?: timesheet.project?.id, | |||
taskId = timesheet.projectTask?.task?.id, | |||
taskGroupId = timesheet.projectTask?.task?.taskGroup?.id, | |||
inputHours = timesheet.normalConsumed ?: 0.0, | |||
otHours = timesheet.otConsumed ?: 0.0, | |||
remark = timesheet.remark | |||
) | |||
} } | |||
.mapValues { (_, timesheets) -> | |||
timesheets.map { timesheet -> | |||
TimeEntry( | |||
id = timesheet.id!!, | |||
projectId = timesheet.projectTask?.project?.id ?: timesheet.project?.id, | |||
taskId = timesheet.projectTask?.task?.id, | |||
taskGroupId = timesheet.projectTask?.task?.taskGroup?.id, | |||
inputHours = timesheet.normalConsumed ?: 0.0, | |||
otHours = timesheet.otConsumed ?: 0.0, | |||
remark = timesheet.remark | |||
) | |||
} | |||
} | |||
} | |||
private fun mergeTimeEntriesByProjectAndTask(entries: List<TimeEntry>): List<TimeEntry> { | |||
return entries | |||
.groupBy { timeEntry -> Pair(timeEntry.projectId, timeEntry.taskId) } | |||
.values.map { timeEntries -> | |||
timeEntries.reduce { acc, timeEntry -> acc.copy( | |||
inputHours = (acc.inputHours ?: 0.0) + (timeEntry.inputHours ?: 0.0), | |||
otHours = (acc.otHours ?: 0.0) + (timeEntry.otHours ?: 0.0) | |||
) } | |||
timeEntries.reduce { acc, timeEntry -> | |||
acc.copy( | |||
inputHours = (acc.inputHours ?: 0.0) + (timeEntry.inputHours ?: 0.0), | |||
otHours = (acc.otHours ?: 0.0) + (timeEntry.otHours ?: 0.0) | |||
) | |||
} | |||
} | |||
} | |||
@Transactional(rollbackFor = [Exception::class]) | |||
open fun importFile(workbook: Workbook?): String { | |||
val logger = LogFactory.getLog(javaClass) | |||
if (workbook == null) { | |||
return "No Excel import" // if workbook is null | |||
} | |||
val notExistProjectList = mutableListOf<String>() | |||
val sheet: Sheet = workbook.getSheetAt(0) | |||
logger.info("---------Start Import Timesheets-------") | |||
val timesheetList = mutableListOf<Timesheet>().toMutableList(); | |||
for (i in 1..<sheet.lastRowNum) { | |||
val row = sheet.getRow(i) | |||
if (row?.getCell(0) != null && !row.getCell(0).stringCellValue.isNullOrBlank() && row.getCell(3) != null && !row.getCell( | |||
3 | |||
).stringCellValue.isNullOrBlank() | |||
) { | |||
logger.info("row :$i | lastCellNum" + row.lastCellNum) | |||
// process staff | |||
logger.info("---------staff-------") | |||
var staff = staffRepository.findByStaffId(row.getCell(0).stringCellValue).getOrNull() | |||
if (staff == null) { | |||
staff = staffRepository.findByStaffId("B000").orElseThrow() | |||
} | |||
// process project | |||
logger.info("---------project-------") | |||
var projectCode = StringBuilder(row.getCell(3).stringCellValue).insert(1, '-').toString() | |||
if (row.getCell(4) != null && row.getCell(4).toString().isNotBlank()) { | |||
val subCode = row.getCell(4).numericCellValue | |||
val splitMainProjectCode = projectCode.split('-') | |||
projectCode = splitMainProjectCode[0] + '-' + String.format( | |||
"%04d", | |||
splitMainProjectCode[1].toInt() | |||
) + '-' + String.format("%03d", subCode.toInt()) | |||
} else { | |||
val splitProjectCode = projectCode.split('-') | |||
projectCode = splitProjectCode[0] + '-' + String.format("%04d", splitProjectCode[1].toInt()) | |||
} | |||
logger.info("Project Code: $projectCode") | |||
val project = projectRepository.findByCode(projectCode) | |||
// process project task | |||
logger.info("---------project task-------") | |||
val task = taskRepository.findById(41).getOrNull() | |||
val projectTask = | |||
project?.let { p -> task?.let { t -> projectTaskRepository.findByProjectAndTask(p, t) } } | |||
// process record date | |||
logger.info("---------record date-------") | |||
val formatter = DateTimeFormatter.ofPattern("dd-MMM-yyyy") | |||
val recordDate = LocalDate.parse(row.getCell(6).toString(), formatter); | |||
// normal hour + ot hour | |||
logger.info("---------normal hour + ot hour-------") | |||
val hours: Double = row.getCell(7).numericCellValue | |||
val normalHours = if (hours > 8.0) 8.0 else hours | |||
val otHours = if (hours > 8.0) hours - 8.0 else 0.0 | |||
if (project != null) { | |||
timesheetList += Timesheet().apply { | |||
this.staff = staff | |||
this.recordDate = recordDate | |||
this.normalConsumed = normalHours | |||
this.otConsumed = otHours | |||
this.projectTask = projectTask | |||
this.project = project | |||
} | |||
} else { | |||
notExistProjectList += projectCode | |||
} | |||
} | |||
} | |||
timesheetRepository.saveAll(timesheetList) | |||
logger.info("---------end-------") | |||
logger.info("Not Exist Project List: "+ notExistProjectList.distinct().joinToString(", ")) | |||
return if (sheet.lastRowNum > 0) "Import Excel success btw " + notExistProjectList.joinToString(", ") else "Import Excel failure" | |||
} | |||
} |
@@ -5,12 +5,18 @@ import com.ffii.tsms.modules.timesheet.entity.LeaveType | |||
import com.ffii.tsms.modules.timesheet.service.LeaveService | |||
import com.ffii.tsms.modules.timesheet.service.TimesheetsService | |||
import com.ffii.tsms.modules.timesheet.web.models.* | |||
import jakarta.servlet.http.HttpServletRequest | |||
import jakarta.validation.Valid | |||
import org.apache.poi.ss.usermodel.Workbook | |||
import org.apache.poi.xssf.usermodel.XSSFWorkbook | |||
import org.springframework.http.ResponseEntity | |||
import org.springframework.web.bind.ServletRequestBindingException | |||
import org.springframework.web.bind.annotation.GetMapping | |||
import org.springframework.web.bind.annotation.PostMapping | |||
import org.springframework.web.bind.annotation.RequestBody | |||
import org.springframework.web.bind.annotation.RequestMapping | |||
import org.springframework.web.bind.annotation.RestController | |||
import org.springframework.web.multipart.MultipartHttpServletRequest | |||
import java.time.LocalDate | |||
import java.time.format.DateTimeFormatter | |||
@@ -85,4 +91,20 @@ class TimesheetsController(private val timesheetsService: TimesheetsService, pri | |||
fun leaveTypes(): List<LeaveType> { | |||
return leaveService.getLeaveTypes() | |||
} | |||
@PostMapping("/import") | |||
@Throws(ServletRequestBindingException::class) | |||
fun importFile(request: HttpServletRequest): ResponseEntity<*> { | |||
var workbook: Workbook? = null | |||
try { | |||
val multipartFile = (request as MultipartHttpServletRequest).getFile("multipartFileList") | |||
workbook = XSSFWorkbook(multipartFile?.inputStream) | |||
} catch (e: Exception) { | |||
println("Excel Wrong") | |||
println(e) | |||
} | |||
return ResponseEntity.ok(timesheetsService.importFile(workbook)) | |||
} | |||
} |
@@ -229,6 +229,13 @@ public class UserService extends AbstractBaseEntityService<User, Long, UserRepos | |||
List<Map<String, Integer>> authBatchDeleteValues = req.getRemoveAuthIds().stream() | |||
.map(authId -> Map.of("userId", (int)id, "authId", authId)) | |||
.collect(Collectors.toList()); | |||
if (!authBatchDeleteValues.isEmpty()) { | |||
jdbcDao.batchUpdate( | |||
"DELETE FROM user_authority" | |||
+ " WHERE userId = :userId ", | |||
// + "AND authId = :authId", | |||
authBatchDeleteValues); | |||
} | |||
if (!authBatchInsertValues.isEmpty()) { | |||
jdbcDao.batchUpdate( | |||
"INSERT IGNORE INTO user_authority (userId, authId)" | |||
@@ -236,13 +243,6 @@ public class UserService extends AbstractBaseEntityService<User, Long, UserRepos | |||
authBatchInsertValues); | |||
} | |||
if (!authBatchDeleteValues.isEmpty()) { | |||
jdbcDao.batchUpdate( | |||
"DELETE FROM user_authority" | |||
+ " WHERE userId = :userId ", | |||
// + "AND authId = :authId", | |||
authBatchDeleteValues); | |||
} | |||
return instance; | |||
} | |||
@@ -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`; |