@@ -2,8 +2,12 @@ package com.ffii.tsms.config.security.jwt.web; | |||||
import java.time.Instant; | import java.time.Instant; | ||||
import java.util.HashSet; | import java.util.HashSet; | ||||
import java.util.Map; | |||||
import java.util.Set; | import java.util.Set; | ||||
import com.ffii.tsms.modules.data.entity.Staff; | |||||
import com.ffii.tsms.modules.data.entity.StaffRepository; | |||||
import com.ffii.tsms.modules.user.service.GroupService; | |||||
import org.apache.commons.lang3.exception.ExceptionUtils; | import org.apache.commons.lang3.exception.ExceptionUtils; | ||||
import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
import org.springframework.beans.factory.annotation.Qualifier; | import org.springframework.beans.factory.annotation.Qualifier; | ||||
@@ -51,6 +55,12 @@ public class JwtAuthenticationController { | |||||
@Autowired | @Autowired | ||||
private JwtUserDetailsService userDetailsService; | private JwtUserDetailsService userDetailsService; | ||||
@Autowired | |||||
private GroupService groupService; | |||||
@Autowired | |||||
private StaffRepository staffRepository; | |||||
@Autowired | @Autowired | ||||
private UserRepository userRepository; | private UserRepository userRepository; | ||||
@@ -90,10 +100,14 @@ public class JwtAuthenticationController { | |||||
final String accessToken = jwtTokenUtil.generateToken(user); | final String accessToken = jwtTokenUtil.generateToken(user); | ||||
final String refreshToken = jwtTokenUtil.createRefreshToken(user.getUsername()).getToken(); | final String refreshToken = jwtTokenUtil.createRefreshToken(user.getUsername()).getToken(); | ||||
final Map<String, Object> args = Map.of("userId", user.getId()); | |||||
final String role = groupService.getGroupName(args); | |||||
final Staff staff = staffRepository.findIdAndNameByUserIdAndDeletedFalse(user.getId()).orElse(null); | |||||
Set<AbilityModel> abilities = new HashSet<>(); | Set<AbilityModel> abilities = new HashSet<>(); | ||||
userAuthorityService.getUserAuthority(user).forEach(auth -> abilities.add(new AbilityModel(auth.getAuthority()))); | userAuthorityService.getUserAuthority(user).forEach(auth -> abilities.add(new AbilityModel(auth.getAuthority()))); | ||||
return ResponseEntity.ok(new JwtResponse(accessToken, refreshToken, null, user, abilities)); | |||||
return ResponseEntity.ok(new JwtResponse(accessToken, refreshToken, role, user, abilities, staff)); | |||||
} | } | ||||
@PostMapping("/refresh-token") | @PostMapping("/refresh-token") | ||||
@@ -3,6 +3,7 @@ package com.ffii.tsms.model; | |||||
import java.io.Serializable; | import java.io.Serializable; | ||||
import java.util.Set; | import java.util.Set; | ||||
import com.ffii.tsms.modules.data.entity.Staff; | |||||
import com.ffii.tsms.modules.user.entity.User; | import com.ffii.tsms.modules.user.entity.User; | ||||
public class JwtResponse implements Serializable { | public class JwtResponse implements Serializable { | ||||
@@ -15,8 +16,11 @@ public class JwtResponse implements Serializable { | |||||
private final String refreshToken; | private final String refreshToken; | ||||
private final String role; | private final String role; | ||||
private final Set<AbilityModel> abilities; | private final Set<AbilityModel> abilities; | ||||
private final Staff staff; | |||||
public JwtResponse(String accessToken, String refreshToken, String role, User user, Set<AbilityModel> abilities) { | |||||
public JwtResponse(String accessToken, String refreshToken, String role, User user, Set<AbilityModel> abilities, Staff staff) { | |||||
this.accessToken = accessToken; | this.accessToken = accessToken; | ||||
this.refreshToken = refreshToken; | this.refreshToken = refreshToken; | ||||
this.role = role; | this.role = role; | ||||
@@ -24,7 +28,8 @@ public class JwtResponse implements Serializable { | |||||
this.name = user.getName(); | this.name = user.getName(); | ||||
this.email = user.getEmail(); | this.email = user.getEmail(); | ||||
this.abilities = abilities; | this.abilities = abilities; | ||||
} | |||||
this.staff = staff; | |||||
} | |||||
public String getAccessToken() { | public String getAccessToken() { | ||||
return this.accessToken; | return this.accessToken; | ||||
@@ -50,6 +55,9 @@ public class JwtResponse implements Serializable { | |||||
return email; | return email; | ||||
} | } | ||||
public Staff getStaff() { return staff; } | |||||
public Set<AbilityModel> getAbilities() { | public Set<AbilityModel> getAbilities() { | ||||
return abilities; | return abilities; | ||||
} | } |
@@ -21,4 +21,6 @@ public interface StaffRepository extends AbstractRepository<Staff, Long> { | |||||
Optional<Staff> findByUserId(@Param("userId") Long userId); | Optional<Staff> findByUserId(@Param("userId") Long userId); | ||||
Optional<List<Staff>> findAllByTeamIdAndDeletedFalse(Long id); | Optional<List<Staff>> findAllByTeamIdAndDeletedFalse(Long id); | ||||
Optional<Staff> findIdAndNameByUserIdAndDeletedFalse(Long id); | |||||
} | } |
@@ -110,8 +110,8 @@ open class DashboardService( | |||||
+ " s.name as teamLead," | + " s.name as teamLead," | ||||
+ " tg.name as expectedStage," | + " tg.name as expectedStage," | ||||
+ " p.totalManhour as budgetedManhour," | + " p.totalManhour as budgetedManhour," | ||||
+ " sum(t.normalConsumed) + sum(t.otConsumed) as spentManhour," | |||||
+ " p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed) as remainedManhour," | |||||
+ " 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 (round(((sum(t.normalConsumed) - sum(t.otConsumed))/p.totalManhour)*100,2),0) as manhourConsumptionPercentage," | ||||
+ " DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d') as comingPaymentMilestone" | + " DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d') as comingPaymentMilestone" | ||||
+ " from project p" | + " from project p" | ||||
@@ -254,8 +254,8 @@ open class DashboardService( | |||||
+ " s.name as teamLead," | + " s.name as teamLead," | ||||
+ " tg.name as expectedStage," | + " tg.name as expectedStage," | ||||
+ " p.totalManhour as budgetedManhour," | + " p.totalManhour as budgetedManhour," | ||||
+ " sum(t.normalConsumed) + sum(t.otConsumed) as spentManhour," | |||||
+ " p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed) as remainedManhour," | |||||
+ " 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 (round(((sum(t.normalConsumed) - sum(t.otConsumed))/p.totalManhour)*100,2),0) as manhourConsumptionPercentage," | ||||
+ " DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d') as comingPaymentMilestone" | + " DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d') as comingPaymentMilestone" | ||||
+ " from project p" | + " from project p" | ||||
@@ -301,7 +301,18 @@ open class DashboardService( | |||||
+ " coalesce(round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2),0) as cpi" | + " coalesce(round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2),0) as cpi" | ||||
+ " from team t" | + " from team t" | ||||
+ " left join project p on t.teamLead = p.teamLead" | + " left join project p on t.teamLead = p.teamLead" | ||||
+ " left join invoice i on p.code = i.projectCode" | |||||
+ " left join (" | |||||
+ " select" | |||||
+ " t3.id as tid," | |||||
+ " sum(i3.issueAmount) as issueAmount," | |||||
+ " sum(i3.paidAmount) as paidAmount" | |||||
+ " from team t3" | |||||
+ " left join project p3 on t3.teamLead = p3.teamLead" | |||||
+ " left join invoice i3 on p3.code = i3.projectCode" | |||||
+ " where t3.deleted = 0" | |||||
+ " and p3.status = 'On-going'" | |||||
+ " group by t3.id" | |||||
+ " ) as i on i.tid = t.id" | |||||
+ " left join (" | + " left join (" | ||||
+ " select" | + " select" | ||||
+ " r.teamId as teamId," | + " r.teamId as teamId," | ||||
@@ -347,7 +358,17 @@ open class DashboardService( | |||||
+ " end as cashFlowStatus," | + " end as cashFlowStatus," | ||||
+ " round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2) as cpi" | + " round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2) as cpi" | ||||
+ " from project p" | + " from project p" | ||||
+ " left join invoice i on p.code = i.projectCode" | |||||
+ " left join (" | |||||
+ " select" | |||||
+ " p3.id as pid," | |||||
+ " sum(i3.issueAmount) as issueAmount," | |||||
+ " sum(i3.paidAmount) as paidAmount" | |||||
+ " from project p3" | |||||
+ " left join invoice i3 on p3.code = i3.projectCode" | |||||
+ " where p3.deleted = 0" | |||||
+ " and p3.status = 'On-going'" | |||||
+ " group by p3.id" | |||||
+ " ) as i on i.pid = p.id" | |||||
+ " left join (" | + " left join (" | ||||
+ " select" | + " select" | ||||
+ " sum(r.cumulativeExpenditure) as cumulativeExpenditure" | + " sum(r.cumulativeExpenditure) as cumulativeExpenditure" | ||||
@@ -399,6 +420,7 @@ open class DashboardService( | |||||
+ " left join customer c on p.customerId = c.id" | + " left join customer c on p.customerId = c.id" | ||||
+ " left join (" | + " left join (" | ||||
+ " select" | + " select" | ||||
+ " t3.id as tid," | |||||
+ " c3.id as cid," | + " c3.id as cid," | ||||
+ " sum(i3.issueAmount) as issueAmount," | + " sum(i3.issueAmount) as issueAmount," | ||||
+ " sum(i3.paidAmount) as paidAmount" | + " sum(i3.paidAmount) as paidAmount" | ||||
@@ -415,8 +437,8 @@ open class DashboardService( | |||||
if (args.containsKey("teamId")) | if (args.containsKey("teamId")) | ||||
sql.append(" AND t3.id = :teamId"); | sql.append(" AND t3.id = :teamId"); | ||||
} | } | ||||
sql.append( " group by c3.id" | |||||
+ " ) as i on i.cid = c.id" | |||||
sql.append( " group by c3.id, t3.id" | |||||
+ " ) as i on i.cid = c.id and i.tid = t.id" | |||||
+ " left join (" | + " left join (" | ||||
+ " select" | + " select" | ||||
+ " r.teamId as teamId," | + " r.teamId as teamId," | ||||
@@ -657,7 +679,17 @@ open class DashboardService( | |||||
+ " coalesce (expenditure.expenditure,0) as totalExpenditure," | + " coalesce (expenditure.expenditure,0) as totalExpenditure," | ||||
+ " coalesce (sum(p.expectedTotalFee)*0.8 - expenditure.expenditure,0) as expenditureReceivable" | + " coalesce (sum(p.expectedTotalFee)*0.8 - expenditure.expenditure,0) as expenditureReceivable" | ||||
+ " from project p" | + " from project p" | ||||
+ " left join invoice i on p.code = i.projectCode" | |||||
+ " left join (" | |||||
+ " select" | |||||
+ " p3.id as pid," | |||||
+ " sum(i3.issueAmount) as issueAmount," | |||||
+ " sum(i3.paidAmount) as paidAmount" | |||||
+ " from project p3" | |||||
+ " left join invoice i3 on p3.code = i3.projectCode" | |||||
+ " where p3.deleted = 0" | |||||
+ " and p3.status = 'On-going'" | |||||
+ " group by p3.id" | |||||
+ " ) as i on i.pid = p.id" | |||||
+ " left join(" | + " left join(" | ||||
+ " select" | + " select" | ||||
+ " sum(r.expenditure) as expenditure" | + " sum(r.expenditure) as expenditure" | ||||
@@ -933,9 +965,12 @@ open class DashboardService( | |||||
+ " p.id as id," | + " p.id as id," | ||||
+ " p.code as projectCode," | + " p.code as projectCode," | ||||
+ " p.name as projectName," | + " p.name as projectName," | ||||
+ " concat(c.code,'-',c.name) as customerCodeAndName" | |||||
+ " concat(c.code,'-',c.name) as customerCodeAndName," | |||||
+ " concat(c.code,'-',c.name) as customerCodeAndName," | |||||
+ " concat(s.code,'-',s.name) as subsidiaryCodeAndName" | |||||
+ " from project p" | + " from project p" | ||||
+ " left join customer c on p.customerId = c.id" | + " left join customer c on p.customerId = c.id" | ||||
+ " left join subsidiary s on p.customerSubsidiaryId = s.id" | |||||
+ " where p.status = 'On-going'" | + " where p.status = 'On-going'" | ||||
+ " and p.deleted = 0" | + " and p.deleted = 0" | ||||
) | ) | ||||
@@ -1033,7 +1068,31 @@ open class DashboardService( | |||||
return jdbcDao.queryForList(sql.toString(), args) | 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" | |||||
) | |||||
return jdbcDao.queryForList(sql.toString(), args) | |||||
} | |||||
} | } | ||||
@@ -232,4 +232,24 @@ class DashboardController( | |||||
result["summarySubStage"] = summarySubStage | result["summarySubStage"] = summarySubStage | ||||
return listOf(result) | return listOf(result) | ||||
} | } | ||||
@GetMapping("/searchMonthlyActualTeamTotalManhoursSpent") | |||||
fun searchMonthlyActualTeamTotalManhoursSpent(request: HttpServletRequest?): List<Map<String, Any>> { | |||||
val args = mutableMapOf<String, Any>() | |||||
val teamId = request?.getParameter("teamId") | |||||
val startdate = request?.getParameter("startdate") | |||||
val enddate = request?.getParameter("enddate") | |||||
if (teamId != null) { | |||||
args["teamId"] = teamId | |||||
} | |||||
if (startdate != null) { | |||||
args["startdate"] = startdate | |||||
} | |||||
if (enddate != null) { | |||||
args["enddate"] = enddate | |||||
} | |||||
val result = mutableMapOf<String, Any>() | |||||
val monthlyActualTeamTotalManhoursSpent = dashboardService.monthlyActualTeamTotalManhoursSpent(args) | |||||
result["monthlyActualTeamTotalManhoursSpent"] = monthlyActualTeamTotalManhoursSpent | |||||
return listOf(result) | |||||
} | |||||
} | } |
@@ -2,44 +2,47 @@ package com.ffii.tsms.modules.data.web | |||||
import com.ffii.core.response.RecordsRes | import com.ffii.core.response.RecordsRes | ||||
import com.ffii.core.utils.CriteriaArgsBuilder | import com.ffii.core.utils.CriteriaArgsBuilder | ||||
import com.ffii.tsms.modules.common.SecurityUtils | |||||
import com.ffii.tsms.modules.data.entity.Skill | import com.ffii.tsms.modules.data.entity.Skill | ||||
import com.ffii.tsms.modules.data.service.SkillService | import com.ffii.tsms.modules.data.service.SkillService | ||||
import com.ffii.tsms.modules.data.web.models.NewSkillRequest | import com.ffii.tsms.modules.data.web.models.NewSkillRequest | ||||
import jakarta.servlet.http.HttpServletRequest | import jakarta.servlet.http.HttpServletRequest | ||||
import jakarta.validation.Valid | import jakarta.validation.Valid | ||||
import org.springframework.http.HttpStatus | import org.springframework.http.HttpStatus | ||||
import org.springframework.security.access.prepost.PreAuthorize | |||||
import org.springframework.web.bind.ServletRequestBindingException | import org.springframework.web.bind.ServletRequestBindingException | ||||
import org.springframework.web.bind.annotation.* | import org.springframework.web.bind.annotation.* | ||||
@RestController | @RestController | ||||
@RequestMapping("/skill") | @RequestMapping("/skill") | ||||
class SkillController(private val skillService: SkillService) { | |||||
open class SkillController(private val skillService: SkillService) { | |||||
@PostMapping("/save") | @PostMapping("/save") | ||||
fun saveSkill(@Valid @RequestBody newSkill: NewSkillRequest): Skill { | |||||
@PreAuthorize("hasAuthority('MAINTAIN_MASTERDATA')") | |||||
open fun saveSkill(@Valid @RequestBody newSkill: NewSkillRequest): Skill { | |||||
return skillService.saveOrUpdate(newSkill) | return skillService.saveOrUpdate(newSkill) | ||||
} | } | ||||
@GetMapping("/{id}") | @GetMapping("/{id}") | ||||
fun list(@Valid @PathVariable id: Long): List<Map<String, Any>> { | |||||
open fun list(@Valid @PathVariable id: Long): List<Map<String, Any>> { | |||||
val args: MutableMap<String, Any> = HashMap() | val args: MutableMap<String, Any> = HashMap() | ||||
args["id"] = id | args["id"] = id | ||||
return skillService.list(args); | return skillService.list(args); | ||||
} | } | ||||
@DeleteMapping("/delete/{id}") | @DeleteMapping("/delete/{id}") | ||||
@ResponseStatus(HttpStatus.NO_CONTENT) | @ResponseStatus(HttpStatus.NO_CONTENT) | ||||
fun delete(@PathVariable id: Long?) { | |||||
open fun delete(@PathVariable id: Long?) { | |||||
skillService.markDelete(id) | skillService.markDelete(id) | ||||
} | } | ||||
@GetMapping | @GetMapping | ||||
fun list(): List<Map<String, Any>> { | |||||
open fun list(): List<Map<String, Any>> { | |||||
val args: MutableMap<String, Any> = HashMap() | val args: MutableMap<String, Any> = HashMap() | ||||
return skillService.list(args); | return skillService.list(args); | ||||
} | } | ||||
@GetMapping("/combo") | @GetMapping("/combo") | ||||
@Throws(ServletRequestBindingException::class) | @Throws(ServletRequestBindingException::class) | ||||
fun combo(request: HttpServletRequest?): RecordsRes<Map<String, Any>> { | |||||
open fun combo(request: HttpServletRequest?): RecordsRes<Map<String, Any>> { | |||||
println(request) | println(request) | ||||
return RecordsRes<Map<String, Any>>( | return RecordsRes<Map<String, Any>>( | ||||
skillService.combo( | skillService.combo( | ||||
@@ -178,8 +178,8 @@ open class ReportService( | |||||
searchedClient: String, | searchedClient: String, | ||||
projects: List<Project>, | projects: List<Project>, | ||||
timesheets: List<Timesheet>, | timesheets: List<Timesheet>, | ||||
numberOfDays: Int, | |||||
projectCompletion: Int, | |||||
daysUntilCurrentStageEnd: Int, | |||||
resourceUtilizationPercentage: Int, | |||||
): ByteArray { | ): ByteArray { | ||||
// Generate the Excel report with query results | // Generate the Excel report with query results | ||||
val workbook: Workbook = createProjectPotentialDelayReport( | val workbook: Workbook = createProjectPotentialDelayReport( | ||||
@@ -187,8 +187,8 @@ open class ReportService( | |||||
searchedClient, | searchedClient, | ||||
projects, | projects, | ||||
timesheets, | timesheets, | ||||
numberOfDays, | |||||
projectCompletion, | |||||
daysUntilCurrentStageEnd, | |||||
resourceUtilizationPercentage, | |||||
PROJECT_POTENTIAL_DELAY_REPORT | PROJECT_POTENTIAL_DELAY_REPORT | ||||
) | ) | ||||
@@ -673,28 +673,43 @@ open class ReportService( | |||||
rowIndex = 4 | rowIndex = 4 | ||||
sheet.getRow(rowIndex).createCell(columnIndex).apply { | sheet.getRow(rowIndex).createCell(columnIndex).apply { | ||||
setCellValue(if (project.customer?.name == null) "N/A" else project.customer?.name) | |||||
setCellValue( | |||||
if (project.customer?.code != null && project.customer?.name != null) project.customer!!.code + " - " + project.customer!!.name | |||||
else if (project.customer?.code != null) project.customer!!.code | |||||
else if (project.customer?.name != null) project.customer!!.name | |||||
else "N/A" | |||||
) | |||||
} | } | ||||
rowIndex = 5 | rowIndex = 5 | ||||
sheet.getRow(rowIndex).createCell(columnIndex).apply { | |||||
setCellValue( | |||||
if (project.customerSubsidiary?.code != null && project.customerSubsidiary?.name != null) project.customerSubsidiary!!.code + " - " + project.customerSubsidiary!!.name | |||||
else if (project.customerSubsidiary?.code != null) project.customerSubsidiary!!.code | |||||
else if (project.customerSubsidiary?.name != null) project.customerSubsidiary!!.name | |||||
else "N/A" | |||||
) | |||||
} | |||||
rowIndex = 6 | |||||
sheet.getRow(rowIndex).createCell(columnIndex).apply { | sheet.getRow(rowIndex).createCell(columnIndex).apply { | ||||
setCellValue(if (project.teamLead?.team?.name == null) "N/A" else project.teamLead?.team?.name) | setCellValue(if (project.teamLead?.team?.name == null) "N/A" else project.teamLead?.team?.name) | ||||
} | } | ||||
rowIndex = 9 | |||||
rowIndex = 10 | |||||
sheet.getRow(rowIndex).apply { | sheet.getRow(rowIndex).apply { | ||||
createCell(1).apply { | createCell(1).apply { | ||||
setCellValue(project.expectedTotalFee!!) | |||||
setCellValue(project.expectedTotalFee!! * 0.8) | |||||
cellStyle.dataFormat = accountingStyle | cellStyle.dataFormat = accountingStyle | ||||
} | } | ||||
createCell(2).apply { | createCell(2).apply { | ||||
setCellValue(project.expectedTotalFee!! / 0.8) | |||||
setCellValue(project.expectedTotalFee!!) | |||||
cellStyle.dataFormat = accountingStyle | cellStyle.dataFormat = accountingStyle | ||||
} | } | ||||
} | } | ||||
rowIndex = 10 | |||||
rowIndex = 11 | |||||
val actualIncome = invoices.sumOf { invoice -> invoice.paidAmount!! } | val actualIncome = invoices.sumOf { invoice -> invoice.paidAmount!! } | ||||
val actualExpenditure = timesheets.sumOf { timesheet -> | val actualExpenditure = timesheets.sumOf { timesheet -> | ||||
timesheet.staff!!.salary.hourlyRate.toDouble() * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed | timesheet.staff!!.salary.hourlyRate.toDouble() * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed | ||||
@@ -712,21 +727,20 @@ open class ReportService( | |||||
} | } | ||||
} | } | ||||
rowIndex = 11 | |||||
rowIndex = 12 | |||||
sheet.getRow(rowIndex).apply { | sheet.getRow(rowIndex).apply { | ||||
createCell(1).apply { | createCell(1).apply { | ||||
cellFormula = "B10-B11" | |||||
cellFormula = "B11-B12" | |||||
cellStyle.dataFormat = accountingStyle | cellStyle.dataFormat = accountingStyle | ||||
} | } | ||||
createCell(2).apply { | createCell(2).apply { | ||||
cellFormula = "C10-C11" | |||||
cellFormula = "C11-C12" | |||||
cellStyle.dataFormat = accountingStyle | cellStyle.dataFormat = accountingStyle | ||||
} | } | ||||
} | } | ||||
rowIndex = 15 | |||||
rowIndex = 16 | |||||
val dateFormatter = | val dateFormatter = | ||||
if (dateType == "Date") DateTimeFormatter.ofPattern("yyyy/MM/dd") else DateTimeFormatter.ofPattern("MMM YYYY") | if (dateType == "Date") DateTimeFormatter.ofPattern("yyyy/MM/dd") else DateTimeFormatter.ofPattern("MMM YYYY") | ||||
@@ -796,7 +810,7 @@ open class ReportService( | |||||
createCell(3).apply { | createCell(3).apply { | ||||
val lastRow = rowIndex - 1 | val lastRow = rowIndex - 1 | ||||
if (lastRow == 15) { | |||||
if (lastRow == 16) { | |||||
cellFormula = | cellFormula = | ||||
"C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString()) | "C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString()) | ||||
} else { | } else { | ||||
@@ -835,7 +849,7 @@ open class ReportService( | |||||
createCell(3).apply { | createCell(3).apply { | ||||
val lastRow = rowIndex - 1 | val lastRow = rowIndex - 1 | ||||
if (lastRow == 15) { | |||||
if (lastRow == 16) { | |||||
cellFormula = "C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString()) | cellFormula = "C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString()) | ||||
} else { | } else { | ||||
cellFormula = | cellFormula = | ||||
@@ -863,8 +877,8 @@ open class ReportService( | |||||
searchedClient: String, | searchedClient: String, | ||||
projects: List<Project>, | projects: List<Project>, | ||||
timesheets: List<Timesheet>, | timesheets: List<Timesheet>, | ||||
numberOfDays: Int, | |||||
projectCompletion: Int, | |||||
daysUntilCurrentStageEnd: Int, | |||||
resourceUtilizationPercentage: Int, | |||||
templatePath: String, | templatePath: String, | ||||
): Workbook { | ): Workbook { | ||||
// please create a new function for each report template | // please create a new function for each report template | ||||
@@ -939,15 +953,29 @@ open class ReportService( | |||||
createCell(4).apply { | createCell(4).apply { | ||||
val currentClient = project.customer | val currentClient = project.customer | ||||
val currentSubsidiary = project.customerSubsidiary | |||||
setCellValue(if (currentSubsidiary != null) currentSubsidiary.code + " - " + currentSubsidiary.name else currentClient?.code + " - " + currentClient?.name) | |||||
setCellValue( | |||||
if (currentClient?.code != null && currentClient.name != null) currentClient.code + " - " + currentClient.name | |||||
else if (currentClient?.code != null) currentClient.code | |||||
else if (currentClient?.name != null) currentClient.name | |||||
else "N/A" | |||||
) | |||||
} | } | ||||
createCell(5).apply { | createCell(5).apply { | ||||
setCellValue(project.actualStart?.format(DATE_FORMATTER)) | |||||
val currentSubsidiary = project.customerSubsidiary | |||||
setCellValue( | |||||
if (currentSubsidiary?.code != null && currentSubsidiary.name != null) currentSubsidiary.code + " - " + currentSubsidiary.name | |||||
else if (currentSubsidiary?.code != null) currentSubsidiary.code | |||||
else if (currentSubsidiary?.name != null) currentSubsidiary.name | |||||
else "N/A" | |||||
) | |||||
} | } | ||||
createCell(6).apply { | createCell(6).apply { | ||||
setCellValue(project.actualStart?.format(DATE_FORMATTER)) | |||||
} | |||||
createCell(7).apply { | |||||
setCellValue(project.planEnd?.format(DATE_FORMATTER)) | setCellValue(project.planEnd?.format(DATE_FORMATTER)) | ||||
} | } | ||||
} | } | ||||
@@ -958,22 +986,22 @@ open class ReportService( | |||||
val manHoursSpent = groupedTimesheets[Pair(project.id, milestone.id)]?.sum() ?: 0.0 | val manHoursSpent = groupedTimesheets[Pair(project.id, milestone.id)]?.sum() ?: 0.0 | ||||
val resourceUtilization = manHoursSpent / (milestone.stagePercentAllocation!! / 100 * project.totalManhour!!) | val resourceUtilization = manHoursSpent / (milestone.stagePercentAllocation!! / 100 * project.totalManhour!!) | ||||
// logger.info(project.name + " : " + milestone.taskGroup?.name + " : " + ChronoUnit.DAYS.between(LocalDate.now(), milestone.endDate)) | // logger.info(project.name + " : " + milestone.taskGroup?.name + " : " + ChronoUnit.DAYS.between(LocalDate.now(), milestone.endDate)) | ||||
// logger.info(numberOfDays) | |||||
if (ChronoUnit.DAYS.between(LocalDate.now(), milestone.endDate) <= numberOfDays.toLong() && resourceUtilization <= projectCompletion.toDouble() / 100.0) { | |||||
// logger.info(daysUntilCurrentStageEnd) | |||||
if (ChronoUnit.DAYS.between(LocalDate.now(), milestone.endDate) <= daysUntilCurrentStageEnd.toLong() && resourceUtilization <= resourceUtilizationPercentage.toDouble() / 100.0) { | |||||
milestoneCount++ | milestoneCount++ | ||||
val tempRow = sheet.getRow(rowIndex) ?: sheet.createRow(rowIndex) | val tempRow = sheet.getRow(rowIndex) ?: sheet.createRow(rowIndex) | ||||
rowIndex++ | rowIndex++ | ||||
tempRow.apply { | tempRow.apply { | ||||
createCell(7).apply { | |||||
createCell(8).apply { | |||||
setCellValue(milestone.taskGroup?.name ?: "N/A") | setCellValue(milestone.taskGroup?.name ?: "N/A") | ||||
} | } | ||||
createCell(8).apply { | |||||
createCell(9).apply { | |||||
setCellValue(milestone.endDate?.format(DATE_FORMATTER) ?: "N/A") | setCellValue(milestone.endDate?.format(DATE_FORMATTER) ?: "N/A") | ||||
} | } | ||||
createCell(9).apply { | |||||
createCell(10).apply { | |||||
cellStyle.dataFormat = workbook.createDataFormat().getFormat("0.00%") | cellStyle.dataFormat = workbook.createDataFormat().getFormat("0.00%") | ||||
// if (groupedTimesheets.containsKey(Pair(project.id, milestone.id))) { | // if (groupedTimesheets.containsKey(Pair(project.id, milestone.id))) { | ||||
@@ -1396,18 +1424,18 @@ open class ReportService( | |||||
generalCreateReportIndexed(sheet, result, rowIndex, columnIndex) | generalCreateReportIndexed(sheet, result, rowIndex, columnIndex) | ||||
val sheetCF = sheet.sheetConditionalFormatting | val sheetCF = sheet.sheetConditionalFormatting | ||||
val rule1 = sheetCF.createConditionalFormattingRule("AND(J7 >= $lowerLimit, J7 <= 1)") | |||||
val rule2 = sheetCF.createConditionalFormattingRule("J7 > 1") | |||||
val rule1 = sheetCF.createConditionalFormattingRule("AND(K7 >= $lowerLimit, K7 <= 1)") | |||||
val rule2 = sheetCF.createConditionalFormattingRule("K7 > 1") | |||||
var fillOrange = rule1.createPatternFormatting() | var fillOrange = rule1.createPatternFormatting() | ||||
fillOrange.setFillBackgroundColor(IndexedColors.LIGHT_ORANGE.index); | fillOrange.setFillBackgroundColor(IndexedColors.LIGHT_ORANGE.index); | ||||
fillOrange.setFillPattern(PatternFormatting.SOLID_FOREGROUND) | fillOrange.setFillPattern(PatternFormatting.SOLID_FOREGROUND) | ||||
var fillRed = rule2.createPatternFormatting() | var fillRed = rule2.createPatternFormatting() | ||||
fillRed.setFillBackgroundColor(IndexedColors.PINK.index); | |||||
fillRed.setFillBackgroundColor(IndexedColors.RED1.index); | |||||
fillRed.setFillPattern(PatternFormatting.SOLID_FOREGROUND) | fillRed.setFillPattern(PatternFormatting.SOLID_FOREGROUND) | ||||
val cfRules = arrayOf(rule1, rule2) | val cfRules = arrayOf(rule1, rule2) | ||||
val regions = arrayOf(CellRangeAddress.valueOf("\$J7:\$K${rowIndex + 1}")) | |||||
val regions = arrayOf(CellRangeAddress.valueOf("\$K7:\$L${rowIndex + 1}")) | |||||
sheetCF.addConditionalFormatting(regions, cfRules); | sheetCF.addConditionalFormatting(regions, cfRules); | ||||
return workbook | return workbook | ||||
@@ -1778,6 +1806,7 @@ open class ReportService( | |||||
+ " p.name, " | + " p.name, " | ||||
+ " t.code as team, " | + " t.code as team, " | ||||
+ " c.code as client, " | + " c.code as client, " | ||||
+ " COALESCE(ss.name, 'N/A') as subsidiary, " | |||||
+ " p.expectedTotalFee * 0.8 as plannedBudget, " | + " p.expectedTotalFee * 0.8 as plannedBudget, " | ||||
+ " COALESCE(tns.totalBudget, 0) as actualConsumedBudget, " | + " COALESCE(tns.totalBudget, 0) as actualConsumedBudget, " | ||||
+ " COALESCE(p.totalManhour, 0) as plannedManhour, " | + " COALESCE(p.totalManhour, 0) as plannedManhour, " | ||||
@@ -1798,6 +1827,7 @@ open class ReportService( | |||||
+ " LEFT JOIN staff s ON p.teamLead = s.id " | + " LEFT JOIN staff s ON p.teamLead = s.id " | ||||
+ " LEFT JOIN salary sa ON s.salaryId = sa.salaryPoint " | + " LEFT JOIN salary sa ON s.salaryId = sa.salaryPoint " | ||||
+ " LEFT JOIN customer c ON p.customerId = c.id " | + " 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 " | + " left join teamNormalConsumed tns on tns.project_id = p.id " | ||||
+ " WHERE p.deleted = false " | + " WHERE p.deleted = false " | ||||
+ " and p.status = 'On-going' " | + " and p.status = 'On-going' " | ||||
@@ -1808,6 +1838,8 @@ open class ReportService( | |||||
sql.append("and t.id = :teamId") | sql.append("and t.id = :teamId") | ||||
if (args.containsKey("custId")) | if (args.containsKey("custId")) | ||||
sql.append("and c.id = :custId") | sql.append("and c.id = :custId") | ||||
if (args.containsKey("subsidiaryId")) | |||||
sql.append("and ss.id = :subsidiaryId") | |||||
if (args.containsKey("status")) | if (args.containsKey("status")) | ||||
statusFilter = when (args.get("status")) { | statusFilter = when (args.get("status")) { | ||||
"Potential Overconsumption" -> "and " + | "Potential Overconsumption" -> "and " + | ||||
@@ -35,6 +35,7 @@ import java.time.format.DateTimeFormatter | |||||
import org.springframework.stereotype.Controller | import org.springframework.stereotype.Controller | ||||
import org.springframework.data.jpa.repository.JpaRepository | import org.springframework.data.jpa.repository.JpaRepository | ||||
import com.ffii.tsms.modules.project.entity.Project | import com.ffii.tsms.modules.project.entity.Project | ||||
import com.ffii.tsms.modules.project.service.SubsidiaryService | |||||
import com.ffii.tsms.modules.report.web.model.* | import com.ffii.tsms.modules.report.web.model.* | ||||
import com.ffii.tsms.modules.timesheet.entity.Timesheet | import com.ffii.tsms.modules.timesheet.entity.Timesheet | ||||
import org.springframework.data.domain.Example | import org.springframework.data.domain.Example | ||||
@@ -56,6 +57,7 @@ class ReportController( | |||||
private val leaveRepository: LeaveRepository, | private val leaveRepository: LeaveRepository, | ||||
private val teamService: TeamService, | private val teamService: TeamService, | ||||
private val customerService: CustomerService, | private val customerService: CustomerService, | ||||
private val subsidiaryService: SubsidiaryService, | |||||
private val invoiceService: InvoiceService, private val gradeAllocationRepository: GradeAllocationRepository, | private val invoiceService: InvoiceService, private val gradeAllocationRepository: GradeAllocationRepository, | ||||
private val subsidiaryRepository: SubsidiaryRepository | private val subsidiaryRepository: SubsidiaryRepository | ||||
) { | ) { | ||||
@@ -119,7 +121,7 @@ class ReportController( | |||||
val projectTasks = projectTaskRepository.findAllByProjectIn(projects) | val projectTasks = projectTaskRepository.findAllByProjectIn(projects) | ||||
val timesheets = timesheetRepository.findAllByProjectTaskIn(projectTasks) | val timesheets = timesheetRepository.findAllByProjectTaskIn(projectTasks) | ||||
val reportResult: ByteArray = excelReportService.generateProjectPotentialDelayReport(searchedTeam, searchedClient, projects, timesheets, request.numberOfDays, request.projectCompletion) | |||||
val reportResult: ByteArray = excelReportService.generateProjectPotentialDelayReport(searchedTeam, searchedClient, projects, timesheets, request.daysUntilCurrentStageEnd, request.resourceUtilizationPercentage) | |||||
return ResponseEntity.ok() | return ResponseEntity.ok() | ||||
.header("filename", "Project Potential Delay Report - " + LocalDate.now() + ".xlsx") | .header("filename", "Project Potential Delay Report - " + LocalDate.now() + ".xlsx") | ||||
.body(ByteArrayResource(reportResult)) | .body(ByteArrayResource(reportResult)) | ||||
@@ -154,7 +156,7 @@ class ReportController( | |||||
fun ProjectResourceOverconsumptionReport(@RequestBody @Valid request: ProjectResourceOverconsumptionReport): ResponseEntity<Resource> { | fun ProjectResourceOverconsumptionReport(@RequestBody @Valid request: ProjectResourceOverconsumptionReport): ResponseEntity<Resource> { | ||||
val lowerLimit = request.lowerLimit | val lowerLimit = request.lowerLimit | ||||
var team: String = "All" | var team: String = "All" | ||||
var customer: String = "All" | |||||
var customerOrSubsidiary: String = "All" | |||||
val args: MutableMap<String, Any> = mutableMapOf( | val args: MutableMap<String, Any> = mutableMapOf( | ||||
"status" to request.status, | "status" to request.status, | ||||
"lowerLimit" to lowerLimit | "lowerLimit" to lowerLimit | ||||
@@ -165,10 +167,14 @@ class ReportController( | |||||
} | } | ||||
if (request.custId != null) { | if (request.custId != null) { | ||||
args["custId"] = request.custId | args["custId"] = request.custId | ||||
customer = customerService.find(request.custId).orElseThrow().name | |||||
customerOrSubsidiary = customerService.find(request.custId).orElseThrow().name | |||||
} | |||||
if (request.subsidiaryId != null) { | |||||
args["subsidiaryId"] = request.subsidiaryId | |||||
customerOrSubsidiary = subsidiaryService.find(request.subsidiaryId).orElseThrow().name | |||||
} | } | ||||
val result: List<Map<String, Any>> = excelReportService.getProjectResourceOverconsumptionReport(args); | val result: List<Map<String, Any>> = excelReportService.getProjectResourceOverconsumptionReport(args); | ||||
val reportResult: ByteArray = excelReportService.generateProjectResourceOverconsumptionReport(team, customer, result, lowerLimit) | |||||
val reportResult: ByteArray = excelReportService.generateProjectResourceOverconsumptionReport(team, customerOrSubsidiary, result, lowerLimit) | |||||
// val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") | // val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") | ||||
return ResponseEntity.ok() | return ResponseEntity.ok() | ||||
// .contentType(mediaType) | // .contentType(mediaType) | ||||
@@ -27,8 +27,8 @@ data class ProjectCashFlowReportRequest ( | |||||
data class ProjectPotentialDelayReportRequest ( | data class ProjectPotentialDelayReportRequest ( | ||||
val teamId: String, | val teamId: String, | ||||
val clientId: String, | val clientId: String, | ||||
val numberOfDays: Int, | |||||
val projectCompletion: Int, | |||||
val daysUntilCurrentStageEnd: Int, | |||||
val resourceUtilizationPercentage: Int, | |||||
val type: String, | val type: String, | ||||
) | ) | ||||
@@ -46,6 +46,7 @@ data class LateStartReportRequest ( | |||||
data class ProjectResourceOverconsumptionReport ( | data class ProjectResourceOverconsumptionReport ( | ||||
val teamId: Long?, | val teamId: Long?, | ||||
val custId: Long?, | val custId: Long?, | ||||
val subsidiaryId: Long?, | |||||
val status: String, | val status: String, | ||||
val lowerLimit: Double | val lowerLimit: Double | ||||
) | ) | ||||
@@ -75,6 +75,17 @@ open class LeaveService( | |||||
return transformToLeaveEntryMap(leaveRepository.findAllByStaff(memberStaff)) | return transformToLeaveEntryMap(leaveRepository.findAllByStaff(memberStaff)) | ||||
} | } | ||||
open fun deleteMemberLeaveEntry(staffId: Long, entryId: Long): Map<String, List<LeaveEntry>> { | |||||
val currentStaff = staffsService.currentStaff() ?: throw BadRequestException() | |||||
// Make sure current staff is a team lead | |||||
teamService.getMyTeamForStaff(currentStaff) ?: throw BadRequestException() | |||||
val memberStaff = staffsService.getStaff(staffId) | |||||
leaveRepository.deleteById(entryId) | |||||
return transformToLeaveEntryMap(leaveRepository.findAllByStaff(memberStaff)) | |||||
} | |||||
open fun getTeamMemberLeave(): Map<Long, TeamMemberLeaveEntries> { | open fun getTeamMemberLeave(): Map<Long, TeamMemberLeaveEntries> { | ||||
val currentStaff = staffsService.currentStaff() ?: return emptyMap() | val currentStaff = staffsService.currentStaff() ?: return emptyMap() | ||||
// Get team where current staff is team lead | // Get team where current staff is team lead | ||||
@@ -83,6 +83,17 @@ open class TimesheetsService( | |||||
return transformToTimeEntryMap(timesheetRepository.findAllByStaff(memberStaff)) | return transformToTimeEntryMap(timesheetRepository.findAllByStaff(memberStaff)) | ||||
} | } | ||||
open fun deleteMemberTimeEntry(staffId: Long, entryId: Long): Map<String, List<TimeEntry>> { | |||||
val currentStaff = staffsService.currentStaff() ?: throw BadRequestException() | |||||
// Make sure current staff is a team lead | |||||
teamService.getMyTeamForStaff(currentStaff) ?: throw BadRequestException() | |||||
val memberStaff = staffsService.getStaff(staffId) | |||||
timesheetRepository.deleteById(entryId) | |||||
return transformToTimeEntryMap(timesheetRepository.findAllByStaff(memberStaff)) | |||||
} | |||||
open fun getTimesheet(): Map<String, List<TimeEntry>> { | open fun getTimesheet(): Map<String, List<TimeEntry>> { | ||||
// Need to be associated with a staff | // Need to be associated with a staff | ||||
val currentStaff = staffsService.currentStaff() ?: return emptyMap() | val currentStaff = staffsService.currentStaff() ?: return emptyMap() | ||||
@@ -66,6 +66,16 @@ class TimesheetsController(private val timesheetsService: TimesheetsService, pri | |||||
}.getOrNull()) | }.getOrNull()) | ||||
} | } | ||||
@PostMapping("/deleteMemberEntry") | |||||
fun deleteMemberEntry(@Valid @RequestBody request: TeamEntryDelete): Map<String, List<TimeEntry>> { | |||||
return timesheetsService.deleteMemberTimeEntry(request.staffId, request.entryId) | |||||
} | |||||
@PostMapping("/deleteMemberLeave") | |||||
fun deleteMemberLeave(@Valid @RequestBody request: TeamEntryDelete): Map<String, List<LeaveEntry>> { | |||||
return leaveService.deleteMemberLeaveEntry(request.staffId, request.entryId) | |||||
} | |||||
@GetMapping("/leaves") | @GetMapping("/leaves") | ||||
fun getLeaveEntry(): Map<String, List<LeaveEntry>> { | fun getLeaveEntry(): Map<String, List<LeaveEntry>> { | ||||
return leaveService.getLeaves() | return leaveService.getLeaves() | ||||
@@ -0,0 +1,6 @@ | |||||
package com.ffii.tsms.modules.timesheet.web.models | |||||
data class TeamEntryDelete( | |||||
val staffId: Long, | |||||
val entryId: Long, | |||||
) |
@@ -4,7 +4,7 @@ import java.util.List; | |||||
import java.util.Optional; | import java.util.Optional; | ||||
import org.springframework.data.repository.query.Param; | import org.springframework.data.repository.query.Param; | ||||
import org.springframework.data.jpa.repository.Query; | |||||
import com.ffii.core.support.AbstractRepository; | import com.ffii.core.support.AbstractRepository; | ||||
public interface UserRepository extends AbstractRepository<User, Long> { | public interface UserRepository extends AbstractRepository<User, Long> { | ||||
@@ -12,5 +12,6 @@ public interface UserRepository extends AbstractRepository<User, Long> { | |||||
List<User> findByName(@Param("name") String name); | List<User> findByName(@Param("name") String name); | ||||
List<User> findAllByAndDeletedFalse(); | List<User> findAllByAndDeletedFalse(); | ||||
Optional<User> findByUsernameAndDeletedFalse(String username); | |||||
Optional<User> findByUsernameAndDeletedFalse(String username); | |||||
} | } |
@@ -172,6 +172,18 @@ public class GroupService extends AbstractBaseEntityService<Group, Long, GroupRe | |||||
+ " WHERE gu.groupId = :id", | + " WHERE gu.groupId = :id", | ||||
Map.of(Params.ID, id)); | Map.of(Params.ID, id)); | ||||
} | } | ||||
@Transactional(rollbackFor = Exception.class) | |||||
public String getGroupName(Map<String, Object> args) { | |||||
StringBuilder sql = new StringBuilder("select" | |||||
+ " g.name " | |||||
+ " from user u " | |||||
+ " left join user_group ug on u.id = ug.userId " | |||||
+ " left join `group`g on ug.groupId = g.id " | |||||
+ " where g.deleted = false " | |||||
+ " and u.id = :userId" | |||||
); | |||||
return jdbcDao.queryForString(sql.toString(), args); | |||||
} | |||||
@Transactional(rollbackFor = Exception.class) | @Transactional(rollbackFor = Exception.class) | ||||
@@ -0,0 +1,8 @@ | |||||
-- liquibase formatted sql | |||||
-- changeset cyril:authority, user_authority | |||||
INSERT INTO authority (authority,name) | |||||
VALUES ('DELETE_PROJECT','delete project'); | |||||
INSERT INTO user_authority (userId,authId) | |||||
VALUES (1,19); |