From 693ad90f7ac6828399a195e0b6295f28e6eda715 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Fri, 19 Jul 2024 12:07:17 +0800 Subject: [PATCH] update report & project --- .../project/entity/ProjectRepository.kt | 3 + .../project/service/ProjectsService.kt | 4 +- .../modules/report/service/ReportService.kt | 399 ++++++++++++++---- .../modules/report/web/ReportController.kt | 12 +- .../report/Cross Team Charge Report.xlsx | Bin 18555 -> 16594 bytes 5 files changed, 320 insertions(+), 98 deletions(-) diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt b/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt index df7cd45..7a55c60 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt @@ -38,5 +38,8 @@ interface ProjectRepository : AbstractRepository { fun findByCode(code: String): Project? + @Query("SELECT p.id FROM Project p WHERE substring_index(substring_index(p.code, '-', 2), '-', -1) like concat('%', substring_index(substring_index(?1, '-', 2), '-', -1), '%')") + fun checkMainProjectByCodeLike(code: String): List? + fun findProjectPlanStartEndByIdAndDeletedFalse(id: Long): ProjectPlanStartEnd } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt b/src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt index 36ee1e3..90fefea 100644 --- a/src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt +++ b/src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt @@ -200,10 +200,10 @@ open class ProjectsService( .orElseThrow() else Project() val duplicateProject = - if (request.projectCode != null) projectRepository.findByCode(request.projectCode) else null + if (request.projectCode != null && request.mainProjectId == null) projectRepository.checkMainProjectByCodeLike(request.projectCode) else null //check duplicate project - if (duplicateProject != null && !duplicateProject.deleted && duplicateProject.id?.equals(request.projectId) == false) { + if (!duplicateProject.isNullOrEmpty() && !duplicateProject.contains(request.projectId)) { return NewProjectResponse( id = request.projectId, code = request.projectCode, diff --git a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt index 69c651a..4f06ab7 100644 --- a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt +++ b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt @@ -684,7 +684,7 @@ open class ReportService( val row10: Row = sheet.getRow(rowNum) val cell6 = row10.createCell(2) cell6.apply { - cellFormula = "O${lastRowNum}" + cellFormula = "P${lastRowNum}" cellStyle.dataFormat = accountingStyle } @@ -692,7 +692,7 @@ open class ReportService( val row11: Row = sheet.getRow(rowNum) val cell7 = row11.createCell(2) cell7.apply { - cellFormula = "P${lastRowNum}" + cellFormula = "Q${lastRowNum}" cellStyle.dataFormat = accountingStyle } @@ -1182,7 +1182,7 @@ open class ReportService( println("----leaves-----") println(leaves) // result = timesheet record mapped - var result: Map = mapOf() + var result: Map = mapOf() if (timesheets.isNotEmpty()) { projectList = timesheets.map { "${it["code"]}\n ${it["name"]}" }.toList().distinct() result = timesheets.groupBy( @@ -1333,7 +1333,7 @@ open class ReportService( // } // } if (totalConsumed.isNotEmpty()) { - totalConsumed.forEach{ t -> + totalConsumed.forEach { t -> val total = t["totalConsumed"] as Double if (total > 8.0) { normalConsumed += 8.0 @@ -1964,16 +1964,18 @@ open class ReportService( ) return jdbcDao.queryForList(sql.toString(), args) } + open fun getTotalConsumed(args: Map): List> { - val sql = StringBuilder("SELECT" - + " CAST(DATE_FORMAT(t.recordDate, '%d') AS SIGNED) AS recordDate, " - + " sum(t.normalConsumed) + sum(t.otConsumed) as totalConsumed " - + " from timesheet t " - + " left join project p on p.id = t.projectId " - + " where t.staffId = :staffId " - + " and t.recordDate BETWEEN :startDate and :endDate " - + " group by t.recordDate " - + " order by t.recordDate; " + val sql = StringBuilder( + "SELECT" + + " CAST(DATE_FORMAT(t.recordDate, '%d') AS SIGNED) AS recordDate, " + + " sum(t.normalConsumed) + sum(t.otConsumed) as totalConsumed " + + " from timesheet t " + + " left join project p on p.id = t.projectId " + + " where t.staffId = :staffId " + + " and t.recordDate BETWEEN :startDate and :endDate " + + " group by t.recordDate " + + " order by t.recordDate; " ) return jdbcDao.queryForList(sql.toString(), args) } @@ -2054,42 +2056,43 @@ open class ReportService( } open fun getProjectResourceOverconsumptionReport(args: Map): List> { - val sql = StringBuilder("SELECT" - + " p.code, " - + " p.name, " - + " tm.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, " - + " sum(t.consumedBudget) as actualConsumedBudget, " - + " COALESCE(p.totalManhour, 0) as plannedManhour, " - + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as actualConsumedManhour, " - + " sum(t.consumedBudget) / p.expectedTotalFee * 0.8 as budgetConsumptionRate, " - + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0) as manhourConsumptionRate, " - + " case " - + " when (sum(t.consumedBudget) / p.expectedTotalFee) >= :lowerLimit and (sum(t.consumedBudget) / p.expectedTotalFee) <= 1 " - + " or (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) >= :lowerLimit and (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) <= 1 " - + " then 'Potential Overconsumption' " - + " when (sum(t.consumedBudget) / p.expectedTotalFee) >= 1 " - + " or (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) >= 1 " - + " then 'Overconsumption' " - + " else 'Within Budget' " - + " END as status " - + " from " - + " (SELECT " - + " t.*, " - + " (t.normalConsumed + COALESCE(t.otConsumed, 0)) * sal.hourlyRate as consumedBudget " - + " from timesheet t " - + " left join staff s on s.id = t.staffId " - + " left join salary sal on sal.salaryPoint = s.salaryId ) t " - + " left join project p on p.id = t.projectId " - + " left join team tm on p.teamLead = tm.teamLead " - + " left join customer c on c.id = p.customerId " - + " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id " - + " WHERE p.deleted = false " - + " and p.status = 'On-going' " + val sql = StringBuilder( + "SELECT" + + " p.code, " + + " p.name, " + + " tm.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, " + + " sum(t.consumedBudget) as actualConsumedBudget, " + + " COALESCE(p.totalManhour, 0) as plannedManhour, " + + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as actualConsumedManhour, " + + " sum(t.consumedBudget) / p.expectedTotalFee * 0.8 as budgetConsumptionRate, " + + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0) as manhourConsumptionRate, " + + " case " + + " when (sum(t.consumedBudget) / p.expectedTotalFee) >= :lowerLimit and (sum(t.consumedBudget) / p.expectedTotalFee) <= 1 " + + " or (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) >= :lowerLimit and (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) <= 1 " + + " then 'Potential Overconsumption' " + + " when (sum(t.consumedBudget) / p.expectedTotalFee) >= 1 " + + " or (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) >= 1 " + + " then 'Overconsumption' " + + " else 'Within Budget' " + + " END as status " + + " from " + + " (SELECT " + + " t.*, " + + " (t.normalConsumed + COALESCE(t.otConsumed, 0)) * sal.hourlyRate as consumedBudget " + + " from timesheet t " + + " left join staff s on s.id = t.staffId " + + " left join salary sal on sal.salaryPoint = s.salaryId ) t " + + " left join project p on p.id = t.projectId " + + " left join team tm on p.teamLead = tm.teamLead " + + " left join customer c on c.id = p.customerId " + + " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id " + + " WHERE p.deleted = false " + + " and p.status = 'On-going' " ) - var statusFilter: String = "" + var statusFilter: String = "" if (args != null) { if (args.containsKey("teamId")) sql.append(" and t.id = :teamId") @@ -2104,14 +2107,16 @@ open class ReportService( " and (sum(t.consumedBudget) / p.expectedTotalFee) <= 1 " + " or (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) >= :lowerLimit " + " and (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) <= 1 " + "All" -> " having " + " (sum(t.consumedBudget) / p.expectedTotalFee) >= :lowerLimit " + " or (sum(t.consumedBudget) / p.expectedTotalFee) >= :lowerLimit " + else -> "" } - } - sql.append(" group by p.code, p.name, tm.code, c.code, c.name, ss.code, ss.name,p.expectedTotalFee, p.totalManhour, p.expectedTotalFee ") - sql.append(statusFilter) + } + sql.append(" group by p.code, p.name, tm.code, c.code, c.name, ss.code, ss.name,p.expectedTotalFee, p.totalManhour, p.expectedTotalFee ") + sql.append(statusFilter) return jdbcDao.queryForList(sql.toString(), args) } @@ -2212,7 +2217,7 @@ open class ReportService( val staffInfoList = mutableListOf>() // println("manHoursSpent------- ${manHoursSpent}") - for (financialYear in financialYears){ + for (financialYear in financialYears) { // println("${financialYear.start.year}-${financialYear.start.monthValue} - ${financialYear.end.year}-${financialYear.end.monthValue}") println("financialYear--------- ${financialYear.start} - ${financialYear.end}") } @@ -2336,7 +2341,11 @@ open class ReportService( // For Calculating the Financial Year data class FinancialYear(val start: YearMonth, val end: YearMonth, val hourlyRate: Double) - fun getFinancialYearDates(startDate: YearMonth, endDate: YearMonth, financialYearStartMonth: Int): Array { + fun getFinancialYearDates( + startDate: YearMonth, + endDate: YearMonth, + financialYearStartMonth: Int + ): Array { val financialYearDates = mutableListOf() var currentYear = startDate.year @@ -3101,9 +3110,9 @@ open class ReportService( } // if (timesheets.isNotEmpty()) { - val combinedTeamCodeColNumber = grades.size * 2 + var combinedTeamCodeColNumber = grades.size * 2 val sortedGrades = grades.sortedBy { it.id } - var sortedTeams = teams.sortedBy { it.id }.toMutableList() + val sortedTeams = teams.sortedBy { it.id }.toMutableList() if (teamId.lowercase() != "all") { val teamIndex = sortedTeams.indexOfFirst { it.id == teamId.toLong() } sortedTeams.add(0, sortedTeams.removeAt(teamIndex)) @@ -3205,12 +3214,20 @@ open class ReportService( createCell(columnIndex++).apply { setCellValue("Total Manhour by Team") - cellStyle = boldFontWithBorderStyle + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + alignment = HorizontalAlignment.CENTER + } } createCell(columnIndex).apply { setCellValue("Total Cost Adjusted by Salary Point by Team") - cellStyle = boldFontWithBorderStyle + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + alignment = HorizontalAlignment.CENTER + } } } @@ -3342,39 +3359,241 @@ open class ReportService( // } conditionalFormattingNegative(sheet) - // -------------------------- sheet 1 (By Detail) -------------------------- // -// sheet = workbook.getSheetAt(1) -// -// val groupedTimesheets2 = timesheets -// .filter { it.project?.teamLead?.team?.id != it.staff?.team?.id } -// .groupBy { timesheetEntry -> -// Pair( -// timesheetEntry.project?.id, -// timesheetEntry.staff?.id -// ) -// } -// .mapValues { (_, timesheetEntries) -> -// timesheetEntries.map { timesheet -> -// if (timesheet.normalConsumed != null) { -// mutableMapOf().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().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().apply { -// this["manHour"] = 0.0 -// this["salary"] = 0.0 -// } -// } -// } -// } + // -------------------------- sheet 1 (Individual) -------------------------- // + sheet = workbook.getSheetAt(1) + + rowIndex = 1 + columnIndex = 2 + sheet.getRow(rowIndex).createCell(columnIndex).apply { + setCellValue(convertReportMonth) + } + + val groupedTimesheetsIndividual = timesheets + .filter { it.project?.teamLead?.team?.id != it.staff?.team?.id } + .groupBy { timesheetEntry -> + Pair( + timesheetEntry.project?.id, + timesheetEntry.staff?.id + ) + } + .mapValues { (_, timesheetEntries) -> + timesheetEntries.map { timesheet -> + if (timesheet.normalConsumed != null) { + mutableMapOf().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().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().apply { + this["manHour"] = 0.0 + this["salary"] = 0.0 + } + } + } + } + + if (sortedTeams.isNotEmpty() && sortedTeams.size > 1) { + rowIndex = 3 + sortedTeams.forEach { team: Team -> + // not his/her team staffs + val staffs = timesheets + .filter { it.project?.teamLead?.team?.id != it.staff?.team?.id && (it.project?.teamLead?.id != team.staff.id || it.staff?.team?.id != team.id) } + .mapNotNull { it.staff } + .sortedBy { it.staffId } + .distinct() + + // his/her team projects + val projects = timesheets + .filter { it.project?.teamLead?.team?.id != it.staff?.team?.id && it.project?.teamLead?.id == team.staff.id } + .mapNotNull { it.project } + .sortedByDescending { it.code } + .distinct() + + // Team + if (!projects.isNullOrEmpty()) { + 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 + staffs.size) + 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) + + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(normalFontWithBorderStyle) + cellStyle = cloneStyle.apply { + alignment = HorizontalAlignment.CENTER + } + } + + } + + // Staffs + sheet.createRow(rowIndex++).apply { + columnIndex = 0 + createCell(columnIndex++).apply { + setCellValue("") + cellStyle = boldFontWithBorderStyle + } + + + staffs.forEach { staff: Staff -> + createCell(columnIndex++).apply { + setCellValue("${staff.staffId} - ${staff.name} (${staff.team.code})") + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + alignment = HorizontalAlignment.CENTER + } + + sheet.setColumnWidth(this.columnIndex, 50 * 256) + } + } + + createCell(columnIndex++).apply { + setCellValue("Total Manhour by Project") + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + alignment = HorizontalAlignment.CENTER + } + sheet.setColumnWidth(this.columnIndex, 50 * 256) + } + + createCell(columnIndex).apply { + setCellValue("Total Cost Adjusted by Salary Point by Project") + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + alignment = HorizontalAlignment.CENTER + } + sheet.setColumnWidth(this.columnIndex, 50 * 256) + } + } + + // Project + Manhour + val startRow = rowIndex + 1 + var endRow = rowIndex + projects.forEach { project: Project -> + if (teamId.lowercase() == "all" || teamId.toLong() == project.teamLead?.team?.id || teamId.toLong() == team.id) { + if (team.id == project.teamLead?.team?.id) { + endRow++ + sheet.createRow(rowIndex++).apply { + columnIndex = 0 + createCell(columnIndex++).apply { + setCellValue("${project.code}: ${project.name}") + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(normalFontWithBorderStyle) + cellStyle = cloneStyle.apply { + alignment = HorizontalAlignment.LEFT + } + } + + var totalSalary = 0.0 + staffs.forEach { staff: Staff -> + logger.info("Staff: ${staff.staffId}") + createCell(columnIndex++).apply { + setCellValue( + groupedTimesheetsIndividual[Pair( + project.id, + staff.id, + )]?.sumOf { it.getValue("manHour") } ?: 0.0) + + totalSalary += groupedTimesheetsIndividual[Pair( + project.id, + staff.id + )]?.sumOf { it.getValue("salary") } ?: 0.0 + + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(normalFontWithBorderStyle) + cellStyle = cloneStyle.apply { + dataFormat = accountingStyle + } + } + } + + createCell(columnIndex++).apply { + val lastCellLetter = CellReference.convertNumToColString(this.columnIndex - 1) + cellFormula = "sum(B${this.rowIndex + 1}:${lastCellLetter}${this.rowIndex + 1})" + + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + dataFormat = accountingStyle + } + } + + createCell(columnIndex).apply { + setCellValue(totalSalary) + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + dataFormat = accountingStyle + } + } + } + } + } + } + + // Total Manhour & Cost + sheet.createRow(rowIndex).apply { + columnIndex = 0 + createCell(columnIndex++).apply { + setCellValue("") + cellStyle = boldFontWithBorderStyle + } + + staffs.forEach { staff: Staff -> + createCell(columnIndex++).apply { + setCellValue("") + cellStyle = boldFontWithBorderStyle + } + } + + createCell(columnIndex++).apply { + val lastCellLetter = CellReference.convertNumToColString(this.columnIndex) + cellFormula = "sum(${lastCellLetter}${startRow}:${lastCellLetter}${endRow})" + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + dataFormat = accountingStyle + } + } + + createCell(columnIndex).apply { + val lastCellLetter = CellReference.convertNumToColString(this.columnIndex) + cellFormula = "sum(${lastCellLetter}${startRow}:${lastCellLetter}${endRow})" + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + dataFormat = accountingStyle + } + } + } + + rowIndex += 2 + } + } + } + + conditionalFormattingNegative(sheet) return workbook } diff --git a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt index 6c98fec..8f858ba 100644 --- a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt +++ b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt @@ -316,9 +316,9 @@ class ReportController( @Throws(ServletRequestBindingException::class, IOException::class) fun getCrossTeamChargeReport(@RequestBody @Valid request: CrossTeamChargeReportRequest): ResponseEntity { - val authorities = staffsService.currentAuthorities() ?: return ResponseEntity.noContent().build() - - if (authorities.stream().anyMatch { it.authority.equals("GENERATE_CROSS_TEAM_CHARGE_REPORT") }) { +// val authorities = staffsService.currentAuthorities() ?: return ResponseEntity.noContent().build() +// +// if (authorities.stream().anyMatch { it.authority.equals("G_CROSS_TEAM_CHARGE_REPORT") }) { // val crossProjects = staffAllocationRepository.findAll() // .filter { it.project?.teamLead?.team?.id != it.staff?.team?.id } // .map { it.project!! } @@ -342,8 +342,8 @@ class ReportController( .header("filename", "Cross Team Charge Report - " + LocalDate.now() + ".xlsx") .body(ByteArrayResource(reportResult)) - } else { - return ResponseEntity.noContent().build() - } +// } else { +// return ResponseEntity.noContent().build() +// } } } \ No newline at end of file diff --git a/src/main/resources/templates/report/Cross Team Charge Report.xlsx b/src/main/resources/templates/report/Cross Team Charge Report.xlsx index 1a4889b5f76dbfa6621382f37ca1a82bcb1d7c7b..54ec3cf9b517bd86b16415630a4a2a5e3284e9de 100644 GIT binary patch delta 6455 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 EA3&aT-T(jq