commit b9ac5ae0b29311fda54c7ee71b6776bbfd75cda9 Author: Your NamebaishaliHolocron Date: Mon Jun 15 12:57:03 2026 +0530 first commit diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..839c2ac --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,39 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +../.vscode/ + +../logs/ +logs/ + + diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..c604e7b --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,51 @@ +# Stage 1: Build +FROM maven:3.9-eclipse-temurin-21 AS builder +WORKDIR /build + +# Copy the parent pom and all module poms first for better layer caching +COPY backend/pom.xml ./pom.xml +COPY backend/projectmanagement-client/pom.xml projectmanagement-client/ +COPY backend/projectmanagement-server/pom.xml projectmanagement-server/ + + +RUN mvn dependency:go-offline -B + +# Copy the entire backend source code +COPY backend/ . + +# 4. Build the application +RUN mvn -pl projectmanagement-server -am clean package \ + -DskipTests -Dmaven.javadoc.skip=true + + +# ======================= +# Runtime Image +# ======================= +FROM eclipse-temurin:21-jre-alpine +WORKDIR /app + +# Create user and group +RUN addgroup -S spring && adduser -S spring -G spring + +# Copy the built JAR from builder stage +COPY --from=builder /build/projectmanagement-server/target/*.jar app.jar + +# Create directories and set permissions +RUN mkdir -p /app/logs /app/bpmn && \ + chown -R spring:spring /app/logs /app + +# Copy BPMN files +# NOTE: This expects a 'bpmn' folder in your project root +COPY --chown=spring:spring bpmn/ /app/bpmn/ + + +USER spring +EXPOSE 8071 + +# Environment setup +ENV IKON_ACCESSMANAGEMENT_INIT_FILE=/app/bpmn/project.json +ENV IKON_PROCESSENGINE_BPMN_PATH=/app/bpmn +ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0" + +# Run the application +ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"] diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..5d25657 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,17 @@ +# Getting Started + +### Reference Documentation +For further reference, please consider the following sections: + +* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) +* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/3.4.4/maven-plugin) +* [Create an OCI image](https://docs.spring.io/spring-boot/3.4.4/maven-plugin/build-image.html) +* [Spring Boot DevTools](https://docs.spring.io/spring-boot/3.4.4/reference/using/devtools.html) + +### Maven Parent overrides + +Due to Maven's design, elements are inherited from the parent POM to the project POM. +While most of the inheritance is fine, it also inherits unwanted elements like `` and `` from the parent. +To prevent this, the project POM contains empty overrides for these elements. +If you manually switch to a different parent and actually want the inheritance, you need to remove those overrides. + diff --git a/backend/ikoncloud-dev.keross.com.crt b/backend/ikoncloud-dev.keross.com.crt new file mode 100644 index 0000000..dab5883 Binary files /dev/null and b/backend/ikoncloud-dev.keross.com.crt differ diff --git a/backend/lib/ikon-app-access-management-1.0.0.jar b/backend/lib/ikon-app-access-management-1.0.0.jar new file mode 100644 index 0000000..041d8ef Binary files /dev/null and b/backend/lib/ikon-app-access-management-1.0.0.jar differ diff --git a/backend/lib/ikon-app-access-management-events-1.0.0.jar b/backend/lib/ikon-app-access-management-events-1.0.0.jar new file mode 100644 index 0000000..ef41f7f Binary files /dev/null and b/backend/lib/ikon-app-access-management-events-1.0.0.jar differ diff --git a/backend/lib/ikon-app-core-1.0.0.jar b/backend/lib/ikon-app-core-1.0.0.jar new file mode 100644 index 0000000..a5d6635 Binary files /dev/null and b/backend/lib/ikon-app-core-1.0.0.jar differ diff --git a/backend/lib/ikon-autoconfigure-1.0.0.jar b/backend/lib/ikon-autoconfigure-1.0.0.jar new file mode 100644 index 0000000..064d62a Binary files /dev/null and b/backend/lib/ikon-autoconfigure-1.0.0.jar differ diff --git a/backend/lib/ikon-client-1.0.0.jar b/backend/lib/ikon-client-1.0.0.jar new file mode 100644 index 0000000..0ca8fb4 Binary files /dev/null and b/backend/lib/ikon-client-1.0.0.jar differ diff --git a/backend/lib/ikon-core-1.0.0.jar b/backend/lib/ikon-core-1.0.0.jar new file mode 100644 index 0000000..351ecc6 Binary files /dev/null and b/backend/lib/ikon-core-1.0.0.jar differ diff --git a/backend/lib/ikon-dataaccess-1.0.0.jar b/backend/lib/ikon-dataaccess-1.0.0.jar new file mode 100644 index 0000000..311b00e Binary files /dev/null and b/backend/lib/ikon-dataaccess-1.0.0.jar differ diff --git a/backend/lib/ikon-logger-events-1.0.0.jar b/backend/lib/ikon-logger-events-1.0.0.jar new file mode 100644 index 0000000..c9df53e Binary files /dev/null and b/backend/lib/ikon-logger-events-1.0.0.jar differ diff --git a/backend/lib/ikon-platform-1.0.0.jar b/backend/lib/ikon-platform-1.0.0.jar new file mode 100644 index 0000000..2ad2c83 Binary files /dev/null and b/backend/lib/ikon-platform-1.0.0.jar differ diff --git a/backend/lib/ikon-platform-server-1.0.0.jar b/backend/lib/ikon-platform-server-1.0.0.jar new file mode 100644 index 0000000..6a58040 Binary files /dev/null and b/backend/lib/ikon-platform-server-1.0.0.jar differ diff --git a/backend/lib/ikon-platform-server-events-1.0.0.jar b/backend/lib/ikon-platform-server-events-1.0.0.jar new file mode 100644 index 0000000..024f052 Binary files /dev/null and b/backend/lib/ikon-platform-server-events-1.0.0.jar differ diff --git a/backend/lib/ikon-processengine-1.0.0.jar b/backend/lib/ikon-processengine-1.0.0.jar new file mode 100644 index 0000000..95922b9 Binary files /dev/null and b/backend/lib/ikon-processengine-1.0.0.jar differ diff --git a/backend/lib/ikon-processmanagement-1.0.0.jar b/backend/lib/ikon-processmanagement-1.0.0.jar new file mode 100644 index 0000000..cfbbb14 Binary files /dev/null and b/backend/lib/ikon-processmanagement-1.0.0.jar differ diff --git a/backend/lib/ikon-sdk-1.0.0.jar b/backend/lib/ikon-sdk-1.0.0.jar new file mode 100644 index 0000000..c834c90 Binary files /dev/null and b/backend/lib/ikon-sdk-1.0.0.jar differ diff --git a/backend/lib/ikon-web-context-manager-1.0.0.jar b/backend/lib/ikon-web-context-manager-1.0.0.jar new file mode 100644 index 0000000..369fc43 Binary files /dev/null and b/backend/lib/ikon-web-context-manager-1.0.0.jar differ diff --git a/backend/lib/ikon-web-security-1.0.0.jar b/backend/lib/ikon-web-security-1.0.0.jar new file mode 100644 index 0000000..529d8c4 Binary files /dev/null and b/backend/lib/ikon-web-security-1.0.0.jar differ diff --git a/backend/lib/ikon-webservice-1.0.0.jar b/backend/lib/ikon-webservice-1.0.0.jar new file mode 100644 index 0000000..97ecfe8 Binary files /dev/null and b/backend/lib/ikon-webservice-1.0.0.jar differ diff --git a/backend/lib/kafka-utils-1.0.0.jar b/backend/lib/kafka-utils-1.0.0.jar new file mode 100644 index 0000000..2e257a0 Binary files /dev/null and b/backend/lib/kafka-utils-1.0.0.jar differ diff --git a/backend/pom.xml b/backend/pom.xml new file mode 100644 index 0000000..fc6c3f3 --- /dev/null +++ b/backend/pom.xml @@ -0,0 +1,95 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.4.4 + + + com.ikon.projectmanagement + projectmanagement + 1.0.0 + pom + + projectmanagement + + + + + + + + + + + + + + + 21 + 21 + 2024.0.0 + 24.1.1 + 1.12.0 + 7.8.0 + 1.0.0 + 1.0.0 + + + + + maven-central + Maven Central Repository + https://ikon-vpm.keross.com/nexus/repository/maven-central + + true + + + false + + + + ikon-nexus + Ikon Repository + https://ikon-vpm.keross.com/nexus/repository/maven-releases + + true + + + false + + + + + + + + ikon-apps-lib-dist + Ikon App Lib Repository + https://ikon-vpm.keross.com/nexus/repository/ikon-apps-lib-dist/ + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + projectmanagement-client + projectmanagement-server + + diff --git a/backend/projectmanagement-client/pom.xml b/backend/projectmanagement-client/pom.xml new file mode 100644 index 0000000..efb8d12 --- /dev/null +++ b/backend/projectmanagement-client/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + + com.ikon.projectmanagement + projectmanagement + 1.0.0 + + projectmanagement-client + + + com.ikon + ikon-core + ${ikon.sdk.version} + + + org.springframework.boot + spring-boot-starter-web + + + org.springdoc + springdoc-openapi-ui + 1.8.0 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-validation + + + org.projectlombok + lombok + true + + + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + + + \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/DashboardApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/DashboardApi.java new file mode 100644 index 0000000..6d62111 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/DashboardApi.java @@ -0,0 +1,36 @@ +package com.ikon.projectmanagement.api; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import com.ikon.projectmanagement.dto.response.DashboardWidgetsResponseDto; +import com.ikon.projectmanagement.dto.response.StatusWiseProjectResponseData; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Dashboard", description = "APIs for managing dashboard") +@RequestMapping("/api/v1/dashboard") +public interface DashboardApi { + @Operation(summary = "Get dashboard widgets data", description = "Retrieves the data for the dashboard widgets.", responses = { + @ApiResponse(responseCode = "200", description = "Widgets data retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/widgets") + ResponseEntity getWidgetsData( + @RequestHeader("Authorization") String accessToken); + + @Operation(summary = "Get status-wise project data", description = "Retrieves the project data based on their status.", responses = { + @ApiResponse(responseCode = "200", description = "Project data retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/status-wise-projects") + ResponseEntity> getStatusWiseProjectData( + @RequestHeader("Authorization") String accessToken); +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/EmployeeApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/EmployeeApi.java new file mode 100644 index 0000000..90e3d5d --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/EmployeeApi.java @@ -0,0 +1,44 @@ +package com.ikon.projectmanagement.api; + +import com.ikon.projectmanagement.dto.request.EmployeeRequestDto; +import com.ikon.projectmanagement.dto.response.EmployeeResponseDto; +import java.util.List; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Employee APIs", description = "APIs for managing local employees") +@RequestMapping("/api/v1/employees") +public interface EmployeeApi { + + @Operation(summary = "Get all employees") + @GetMapping + ResponseEntity> getAllEmployees(); + + @Operation(summary = "Get employee by ID") + @GetMapping("/{id}") + ResponseEntity getEmployeeById(@PathVariable String id); + + @Operation(summary = "Create a new employee") + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + ResponseEntity createEmployee(@RequestBody EmployeeRequestDto dto); + + @Operation(summary = "Update an existing employee") + @PutMapping("/{id}") + ResponseEntity updateEmployee(@PathVariable String id, @RequestBody EmployeeRequestDto dto); + + @Operation(summary = "Delete an employee") + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + ResponseEntity deleteEmployee(@PathVariable String id); +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/FxRateApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/FxRateApi.java new file mode 100644 index 0000000..c2d3f6f --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/FxRateApi.java @@ -0,0 +1,40 @@ +package com.ikon.projectmanagement.api; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import com.ikon.projectmanagement.dto.response.FxRateResponseDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.*; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "FX Rate APIs", description = "FX Rate configuration APIs") +@RequestMapping("/api/v1/fx-rate") +public interface FxRateApi { + + @Operation(summary = "Get all FX rate configurations", description = "Fetches all FX rate configurations.", responses = { + @ApiResponse(responseCode = "200", description = "FX rate list fetched successfully"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping + ResponseEntity> getAllFxRates( + @RequestHeader("Authorization") String accessToken, + Pageable pageable); + + @Operation(summary = "Get FX rate by year", description = "Fetches FX rate configuration by year.", parameters = { + @Parameter(name = "year", description = "Identifier of the year") + }, responses = { + @ApiResponse(responseCode = "200", description = "FX rate fetched successfully"), + @ApiResponse(responseCode = "404", description = "FX rate not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/year={year}") + ResponseEntity getFxRateByYear( + @RequestHeader("Authorization") String accessToken, + @PathVariable String year); +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/GradeApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/GradeApi.java new file mode 100644 index 0000000..6925c4a --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/GradeApi.java @@ -0,0 +1,45 @@ +package com.ikon.projectmanagement.api; + +import com.ikon.projectmanagement.dto.request.GradeRequestDto; +import com.ikon.projectmanagement.dto.response.GradeResponseDto; +import java.util.List; +import java.util.UUID; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Grade APIs", description = "APIs for managing local grades") +@RequestMapping("/api/v1/grades") +public interface GradeApi { + + @Operation(summary = "Get all grades") + @GetMapping + ResponseEntity> getAllGrades(); + + @Operation(summary = "Get grade by ID") + @GetMapping("/{id}") + ResponseEntity getGradeById(@PathVariable UUID id); + + @Operation(summary = "Create a new grade") + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + ResponseEntity createGrade(@RequestBody GradeRequestDto dto); + + @Operation(summary = "Update an existing grade") + @PutMapping("/{id}") + ResponseEntity updateGrade(@PathVariable UUID id, @RequestBody GradeRequestDto dto); + + @Operation(summary = "Delete a grade") + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + ResponseEntity deleteGrade(@PathVariable UUID id); +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ImportEmployeeApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ImportEmployeeApi.java new file mode 100644 index 0000000..beec5f3 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ImportEmployeeApi.java @@ -0,0 +1,22 @@ +package com.ikon.projectmanagement.api; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.ikon.projectmanagement.dto.response.EmployeeResponseDto; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Import Employee API", description = "API for importing Employee master data from Sales CRM") +@RequestMapping("/import-data/employees") +public interface ImportEmployeeApi { + + @Operation(summary = "Fetch all employees from Sales CRM and persist locally") + @GetMapping + ResponseEntity> fetchAllEmployees(@RequestHeader("Authorization") String accessToken); +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ImportFxRateApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ImportFxRateApi.java new file mode 100644 index 0000000..a5d95e4 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ImportFxRateApi.java @@ -0,0 +1,22 @@ +package com.ikon.projectmanagement.api; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.ikon.projectmanagement.dto.response.FxRateDto; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Import FX Rate API", description = "API for importing FX Rate master data from Sales CRM") +@RequestMapping("/import-data/fx-rates") +public interface ImportFxRateApi { + + @Operation(summary = "Fetch all FX rates from Sales CRM and persist locally") + @GetMapping + ResponseEntity> fetchAllFxRates(@RequestHeader("Authorization") String accessToken); +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ImportGradeApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ImportGradeApi.java new file mode 100644 index 0000000..a46be61 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ImportGradeApi.java @@ -0,0 +1,22 @@ +package com.ikon.projectmanagement.api; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.ikon.projectmanagement.dto.response.GradeResponseDto; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Import Grade API", description = "API for importing Grade master data from Sales CRM") +@RequestMapping("/import-data/grades") +public interface ImportGradeApi { + + @Operation(summary = "Fetch all grades from Sales CRM and persist locally") + @GetMapping + ResponseEntity> fetchAllGrades(@RequestHeader("Authorization") String accessToken); +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ImportRoleApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ImportRoleApi.java new file mode 100644 index 0000000..364624a --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ImportRoleApi.java @@ -0,0 +1,22 @@ +package com.ikon.projectmanagement.api; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.ikon.projectmanagement.dto.response.RoleResponseDto; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Import Role API", description = "API for importing Role master data from Sales CRM") +@RequestMapping("/import-data/roles") +public interface ImportRoleApi { + + @Operation(summary = "Fetch all roles from Sales CRM and persist locally") + @GetMapping + ResponseEntity> fetchAllRoles(@RequestHeader("Authorization") String accessToken); +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ImportWorkingDayApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ImportWorkingDayApi.java new file mode 100644 index 0000000..1f98aec --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ImportWorkingDayApi.java @@ -0,0 +1,22 @@ +package com.ikon.projectmanagement.api; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.ikon.projectmanagement.dto.response.WorkingDayDto; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Import Working Day API", description = "API for importing Working Day master data from Sales CRM") +@RequestMapping("/import-data/working-days") +public interface ImportWorkingDayApi { + + @Operation(summary = "Fetch all working days from Sales CRM and persist locally") + @GetMapping + ResponseEntity> fetchAllWorkingDays(@RequestHeader("Authorization") String accessToken); +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/IssueApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/IssueApi.java new file mode 100644 index 0000000..e857a28 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/IssueApi.java @@ -0,0 +1,76 @@ +package com.ikon.projectmanagement.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +import java.util.List; +import java.util.UUID; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import com.ikon.projectmanagement.dto.request.IssueCreateRequestDto; +import com.ikon.projectmanagement.dto.response.IssueResponseDto; + +@Tag(name = "Issue APIs", description = "Issue Management related APIs") +@RequestMapping("/api/v1/issues") +public interface IssueApi { + + // ================= CREATE ================= + + @Operation(summary = "Create a new issue", description = "Creates a new issue and returns the created issue details.", requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Issue details", required = true, content = @Content(schema = @Schema(implementation = IssueCreateRequestDto.class))), responses = { + @ApiResponse(responseCode = "201", description = "Issue created successfully"), + @ApiResponse(responseCode = "400", description = "Invalid issue data"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PostMapping + ResponseEntity createIssue( + @RequestHeader("Authorization") String accessToken, + @RequestBody IssueCreateRequestDto issueCreateRequestDto); + + // ================= READ ALL ================= + + @Operation(summary = "Get all issues", description = "Retrieves a list of all existing issues.", responses = { + @ApiResponse(responseCode = "200", description = "Issues retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/project/{projectIdentifier}") + ResponseEntity> getAllIssues( + @RequestHeader("Authorization") String accessToken, + @PathVariable("projectIdentifier") UUID projectIdentifier); + + + // ================= READ BY ID ================= + + @Operation(summary = "Get issue by ID", description = "Retrieves an issue by its unique issue ID.", responses = { + @ApiResponse(responseCode = "200", description = "Issue retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Issue not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/{issueId}") + ResponseEntity getIssueById( + @RequestHeader("Authorization") String accessToken, + @PathVariable("issueId") UUID issueId); + + // ================= UPDATE ================= + + @Operation(summary = "Update an existing issue", description = "Updates the details of an existing issue identified by its ID.", requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Updated issue details", required = true, content = @Content(schema = @Schema(implementation = IssueCreateRequestDto.class))), responses = { + @ApiResponse(responseCode = "200", description = "Issue updated successfully"), + @ApiResponse(responseCode = "400", description = "Invalid issue data or ID provided"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Issue not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PutMapping("/{issueId}") + ResponseEntity updateIssue( + @RequestHeader("Authorization") String accessToken, + @PathVariable("issueId") UUID issueId, + @RequestBody IssueCreateRequestDto issueCreateRequestDto); + +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/MeetingApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/MeetingApi.java new file mode 100644 index 0000000..b207b66 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/MeetingApi.java @@ -0,0 +1,91 @@ +package com.ikon.projectmanagement.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +import java.util.List; +import java.util.UUID; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import com.ikon.projectmanagement.dto.request.MeetingRequestDTO; +import com.ikon.projectmanagement.dto.response.MeetingResponseDTO; + +@Tag(name = "Meeting APIs", description = "Meeting Management related APIs") +@RequestMapping("/api/v1/meetings") +public interface MeetingApi { + + // ================= CREATE ================= + + @Operation(summary = "Create a new meeting", description = "Creates a new meeting and returns the created meeting details.", requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Meeting details", required = true, content = @Content(schema = @Schema(implementation = MeetingRequestDTO.class))), responses = { + @ApiResponse(responseCode = "201", description = "Meeting created successfully"), + @ApiResponse(responseCode = "400", description = "Invalid meeting data"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PostMapping + ResponseEntity createMeeting( + @RequestHeader("Authorization") String accessToken, + @RequestBody MeetingRequestDTO meetingRequestDTO); + + // ================= READ ALL BY PROJECT ID ================= + + @Operation(summary = "Get all meetings by project ID", description = "Retrieves a list of all meetings for a given project using project UUID.", responses = { + @ApiResponse(responseCode = "200", description = "Meetings retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/project/{projectIdentifier}") + ResponseEntity> getAllMeetings( + @RequestHeader("Authorization") String accessToken, + @PathVariable("projectIdentifier") UUID projectIdentifier); + + // ================= READ ALL BY PROJECT IDENTIFIER ================= + + + + // ================= READ BY ID ================= + + @Operation(summary = "Get meeting by ID", description = "Retrieves a meeting by its unique meeting ID.", responses = { + @ApiResponse(responseCode = "200", description = "Meeting retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Meeting not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/{meetingId}") + ResponseEntity getMeetingById( + @RequestHeader("Authorization") String accessToken, + @PathVariable("meetingId") UUID meetingId); + + // ================= UPDATE ================= + + @Operation(summary = "Update an existing meeting", description = "Updates the details of an existing meeting identified by its ID.", requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Updated meeting details", required = true, content = @Content(schema = @Schema(implementation = MeetingRequestDTO.class))), responses = { + @ApiResponse(responseCode = "200", description = "Meeting updated successfully"), + @ApiResponse(responseCode = "400", description = "Invalid meeting data or ID provided"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Meeting not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PutMapping("/{meetingId}") + ResponseEntity updateMeeting( + @RequestHeader("Authorization") String accessToken, + @PathVariable("meetingId") UUID meetingId, + @RequestBody MeetingRequestDTO meetingRequestDTO); + + // ================= DELETE ================= + + @Operation(summary = "Delete a meeting", description = "Deletes a meeting identified by its unique meeting ID.", responses = { + @ApiResponse(responseCode = "204", description = "Meeting deleted successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Meeting not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @DeleteMapping("/{meetingId}") + ResponseEntity deleteMeeting( + @RequestHeader("Authorization") String accessToken, + @PathVariable("meetingId") UUID meetingId); +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ProductOfProjectApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ProductOfProjectApi.java new file mode 100644 index 0000000..4ff0f59 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ProductOfProjectApi.java @@ -0,0 +1,74 @@ +package com.ikon.projectmanagement.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +import java.util.List; +import java.util.UUID; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import com.ikon.projectmanagement.dto.request.ProductOfProjectRequestDto; +import com.ikon.projectmanagement.dto.request.WorkflowTransitionRequestDto; +import com.ikon.projectmanagement.dto.response.ProductOfProjectResponseDto; + +@Tag(name = "Product APIs", description = "Product Of Project Management related APIs") +@RequestMapping("/api/v1/projects") +public interface ProductOfProjectApi { + + @Operation(summary = "Get all products", description = "Retrieves a list of all existing products.", responses = { + @ApiResponse(responseCode = "200", description = "Products retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/{projectIdentifier}/products") + ResponseEntity> getAllProductsByProject( + @RequestHeader("Authorization") String accessToken, + @PathVariable("projectIdentifier") UUID projectIdentifier); + + @Operation(summary = "Get product by product identifier", description = "Retrieves a single product under a project (account + project scoped).", responses = { + @ApiResponse(responseCode = "200", description = "Product retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Product not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/{projectIdentifier}/products/{productIdentifier}") + ResponseEntity getProductById( + @RequestHeader("Authorization") String accessToken, + @PathVariable("projectIdentifier") UUID projectIdentifier, + @PathVariable("productIdentifier") UUID productIdentifier); + + // ================= UPDATE PRODUCT ================= + @Operation(summary = "Update an existing product", description = "Updates a product under a specific project (secure multi-tenant update).", requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Updated product details", required = true, content = @Content(schema = @Schema(implementation = ProductOfProjectRequestDto.class))), responses = { + @ApiResponse(responseCode = "200", description = "Product updated successfully"), + @ApiResponse(responseCode = "400", description = "Invalid product data"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Product not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PutMapping("/{projectIdentifier}/products/{productIdentifier}") + ResponseEntity updateProduct( + @RequestHeader("Authorization") String accessToken, + @PathVariable("projectIdentifier") UUID projectIdentifier, + @PathVariable("productIdentifier") UUID productIdentifier, + @RequestBody ProductOfProjectRequestDto productDto); + + // ================= WORKFLOW STAGE TRANSITION ================= + @Operation(summary = "Transition product workflow stage", description = "Transitions the product to a new workflow stage with validation. The Schedule → Resources & Expenses transition requires schedule data to exist.", requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Transition request with target status", required = true, content = @Content(schema = @Schema(implementation = WorkflowTransitionRequestDto.class))), responses = { + @ApiResponse(responseCode = "200", description = "Transition successful"), + @ApiResponse(responseCode = "400", description = "Transition not allowed or preconditions not met"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Product not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PostMapping("/{projectIdentifier}/products/{productIdentifier}/transition") + ResponseEntity transitionProductStatus( + @RequestHeader("Authorization") String accessToken, + @PathVariable("projectIdentifier") UUID projectIdentifier, + @PathVariable("productIdentifier") UUID productIdentifier, + @RequestBody WorkflowTransitionRequestDto transitionDto); +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ProductResourceAllocationApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ProductResourceAllocationApi.java new file mode 100644 index 0000000..ed01c1a --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ProductResourceAllocationApi.java @@ -0,0 +1,81 @@ +package com.ikon.projectmanagement.api; + +import com.ikon.projectmanagement.dto.request.ProductPSResourceAllocationRequestDto; +import com.ikon.projectmanagement.dto.response.ProductPSResourceAllocationResponseDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@Tag(name = "Product Resource Allocation APIs", description = "Product resource allocation management APIs") +@RequestMapping("/api/v1/products") +public interface ProductResourceAllocationApi { + + @Operation(summary = "Get product resource allocations", description = "Retrieves all resource allocations for a product.", responses = { + @ApiResponse(responseCode = "200", description = "Allocations retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Product not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/{productIdentifier}/resource-allocations") + ResponseEntity> getResourceAllocations( + @RequestHeader("Authorization") String accessToken, + @PathVariable("productIdentifier") UUID productIdentifier); + + @Operation(summary = "Get resource allocation", description = "Retrieves a single resource allocation by its ID.", responses = { + @ApiResponse(responseCode = "200", description = "Allocation retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Allocation not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/{productIdentifier}/resource-allocations/{allocationIdentifier}") + ResponseEntity getResourceAllocation( + @RequestHeader("Authorization") String accessToken, + @PathVariable("productIdentifier") UUID productIdentifier, + @PathVariable("allocationIdentifier") UUID allocationIdentifier); + + @Operation(summary = "Create resource allocation", description = "Creates a new resource allocation for a product.", requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "New allocation details", required = true, content = @Content(schema = @Schema(implementation = ProductPSResourceAllocationRequestDto.class))), responses = { + @ApiResponse(responseCode = "201", description = "Allocation created successfully"), + @ApiResponse(responseCode = "400", description = "Invalid allocation data"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Product not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PostMapping("/{productIdentifier}/resource-allocations") + ResponseEntity createResourceAllocation( + @RequestHeader("Authorization") String accessToken, + @PathVariable("productIdentifier") UUID productIdentifier, + @RequestBody ProductPSResourceAllocationRequestDto allocationRequestDto); + + @Operation(summary = "Update resource allocation", description = "Updates an existing resource allocation for a product.", requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Updated allocation details", required = true, content = @Content(schema = @Schema(implementation = ProductPSResourceAllocationRequestDto.class))), responses = { + @ApiResponse(responseCode = "200", description = "Allocation updated successfully"), + @ApiResponse(responseCode = "400", description = "Invalid allocation data"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Allocation not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PutMapping("/{productIdentifier}/resource-allocations/{allocationIdentifier}") + ResponseEntity updateResourceAllocation( + @RequestHeader("Authorization") String accessToken, + @PathVariable("productIdentifier") UUID productIdentifier, + @PathVariable("allocationIdentifier") UUID allocationIdentifier, + @RequestBody ProductPSResourceAllocationRequestDto allocationRequestDto); + + @Operation(summary = "Delete resource allocation", description = "Deletes a resource allocation from a product.", responses = { + @ApiResponse(responseCode = "204", description = "Allocation deleted successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Allocation not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @DeleteMapping("/{productIdentifier}/resource-allocations/{allocationIdentifier}") + ResponseEntity deleteResourceAllocation( + @RequestHeader("Authorization") String accessToken, + @PathVariable("productIdentifier") UUID productIdentifier, + @PathVariable("allocationIdentifier") UUID allocationIdentifier); +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ProjectApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ProjectApi.java new file mode 100644 index 0000000..c7bfbd9 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ProjectApi.java @@ -0,0 +1,82 @@ +package com.ikon.projectmanagement.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +import java.util.List; +import java.util.UUID; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.ikon.projectmanagement.dto.request.ProjectRequestDto; +import com.ikon.projectmanagement.dto.response.ProjectResponseDto; +import com.ikon.projectmanagement.dto.response.ProjectTimelineResponseDto; + +@Tag(name = "Project APIs", description = "Project Management related APIs") +@RequestMapping("/api/v1/projects") +public interface ProjectApi { + + @Operation(summary = "Create a new project", description = "Creates a new project and returns the created project details.", requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Project details", required = true, content = @Content(schema = @Schema(implementation = ProjectRequestDto.class))), responses = { + @ApiResponse(responseCode = "201", description = "Project created successfully"), + @ApiResponse(responseCode = "400", description = "Invalid project data"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PostMapping + ResponseEntity createProject( + @RequestHeader("Authorization") String accessToken, + @RequestBody ProjectRequestDto projectDto); + + @Operation(summary = "Get all projects", description = "Retrieves a list of all existing projects.", responses = { + @ApiResponse(responseCode = "200", description = "Projects retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping + public ResponseEntity> getAllProjects( + @RequestHeader("Authorization") String accessToken); + + @Operation(summary = "Get project by Id", description = "Retrieves a project by its unique project ID.", responses = { + @ApiResponse(responseCode = "200", description = "Project retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Project not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/{projectIdentifier}") + public ResponseEntity getProjectByProjectIdentifier( + @RequestHeader("Authorization") String accessToken, + @PathVariable("projectIdentifier") UUID projectIdentifier); + + @Operation(summary = "Update an existing project", description = "Updates the details of an existing project identified by its ID.", requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Updated project details", required = true, content = @Content(schema = @Schema(implementation = ProjectRequestDto.class))), responses = { + @ApiResponse(responseCode = "200", description = "Project updated successfully"), + @ApiResponse(responseCode = "400", description = "Invalid project data or ID provided"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Project not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PutMapping("/{projectIdentifier}") + ResponseEntity updateProject( + @RequestHeader("Authorization") String accessToken, + @PathVariable("projectIdentifier") UUID projectIdentifier, + @RequestBody ProjectRequestDto projectDto); + + @Operation(summary = "Get all active projects start and end dates", description = "Retrieves a list of all active projects with their start and end dates.", responses = { + @ApiResponse(responseCode = "200", description = "Projects retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/active-projects-timeline") + public ResponseEntity> getAllActiveProjectsTimeline( + @RequestHeader("Authorization") String accessToken); + +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/RiskApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/RiskApi.java new file mode 100644 index 0000000..b66b383 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/RiskApi.java @@ -0,0 +1,84 @@ +package com.ikon.projectmanagement.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +import java.util.List; +import java.util.UUID; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.ikon.projectmanagement.dto.request.RiskCreateRequestDto; +import com.ikon.projectmanagement.dto.response.RiskResponseDto; + +@Tag(name = "Risk APIs", description = "Risk Management related APIs") +@RequestMapping("/api/v1/risks") +public interface RiskApi { + + @Operation(summary = "Create a new risk", description = "Creates a new risk and returns the created risk details.", requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Risk details", required = true, content = @Content(schema = @Schema(implementation = RiskCreateRequestDto.class))), responses = { + @ApiResponse(responseCode = "201", description = "Risk created successfully"), + @ApiResponse(responseCode = "400", description = "Invalid risk data"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PostMapping + ResponseEntity createRisk( + @RequestHeader("Authorization") String accessToken, + @RequestBody RiskCreateRequestDto riskCreateRequestDto); + + @Operation(summary = "Get all risks", description = "Retrieves a list of all existing risks.", responses = { + @ApiResponse(responseCode = "200", description = "Risks retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping + ResponseEntity> getAllRisks( + @RequestHeader("Authorization") String accessToken); + + @Operation(summary = "Get risk by Id", description = "Retrieves a risk by its unique risk ID.", responses = { + @ApiResponse(responseCode = "200", description = "Risk retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Risk not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/{riskId}") + ResponseEntity getRiskById( + @RequestHeader("Authorization") String accessToken, + @PathVariable("riskId") UUID riskId); + + @Operation(summary = "Update an existing risk", description = "Updates the details of an existing risk identified by its ID.", requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Updated risk details", required = true, content = @Content(schema = @Schema(implementation = RiskCreateRequestDto.class))), responses = { + @ApiResponse(responseCode = "200", description = "Risk updated successfully"), + @ApiResponse(responseCode = "400", description = "Invalid risk data or ID provided"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Risk not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PutMapping("/{riskId}") + ResponseEntity updateRisk( + @RequestHeader("Authorization") String accessToken, + @PathVariable("riskId") UUID riskId, + @RequestBody RiskCreateRequestDto riskCreateRequestDto); + + @Operation(summary = "Get all risks by project", description = "Retrieves a list of all risks associated with a specific project.", responses = { + @ApiResponse(responseCode = "200", description = "Risks retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Project not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/project/{projectIdentifier}") + ResponseEntity> getRisksByProject( + @RequestHeader("Authorization") String accessToken, + @PathVariable("projectIdentifier") String projectIdentifier); + +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/RoleApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/RoleApi.java new file mode 100644 index 0000000..865a295 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/RoleApi.java @@ -0,0 +1,45 @@ +package com.ikon.projectmanagement.api; + +import com.ikon.projectmanagement.dto.request.RoleRequestDto; +import com.ikon.projectmanagement.dto.response.RoleResponseDto; +import java.util.List; +import java.util.UUID; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Role APIs", description = "APIs for managing local roles") +@RequestMapping("/api/v1/roles") +public interface RoleApi { + + @Operation(summary = "Get all roles") + @GetMapping + ResponseEntity> getAllRoles(); + + @Operation(summary = "Get role by ID") + @GetMapping("/{id}") + ResponseEntity getRoleById(@PathVariable UUID id); + + @Operation(summary = "Create a new role") + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + ResponseEntity createRole(@RequestBody RoleRequestDto dto); + + @Operation(summary = "Update an existing role") + @PutMapping("/{id}") + ResponseEntity updateRole(@PathVariable UUID id, @RequestBody RoleRequestDto dto); + + @Operation(summary = "Delete a role") + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + ResponseEntity deleteRole(@PathVariable UUID id); +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ScheduleApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ScheduleApi.java new file mode 100644 index 0000000..07dfd7f --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/ScheduleApi.java @@ -0,0 +1,49 @@ +package com.ikon.projectmanagement.api; + +import com.ikon.projectmanagement.dto.request.ScheduleRequestDto; +import com.ikon.projectmanagement.dto.response.ScheduleResponseDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@Tag(name = "Schedule APIs", description = "Schedule management APIs for a project") +@RequestMapping("/api/v1/projects") +public interface ScheduleApi { + + @Operation(summary = "Save schedule", description = "Replaces the full schedule (tasks + dependencies) for a project. Removed tasks are automatically deleted.", responses = { + @ApiResponse(responseCode = "200", description = "Schedule saved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Project not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PutMapping("/{projectIdentifier}/schedules") + ResponseEntity saveSchedule( + @RequestHeader("Authorization") String accessToken, + @PathVariable("projectIdentifier") UUID projectIdentifier, + @RequestBody ScheduleRequestDto scheduleRequestDto); + + @Operation(summary = "Get schedule", description = "Retrieves the current schedule (tasks + dependencies) for a project.", responses = { + @ApiResponse(responseCode = "200", description = "Schedule retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Project not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/{projectIdentifier}/schedules") + ResponseEntity getSchedule( + @RequestHeader("Authorization") String accessToken, + @PathVariable("projectIdentifier") UUID projectIdentifier); + + @Operation(summary = "Get all schedules", description = "Retrieves schedules for all projects under the current account.", responses = { + @ApiResponse(responseCode = "200", description = "Schedules retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/schedules") + ResponseEntity> getAllSchedules( + @RequestHeader("Authorization") String accessToken); +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/WorkingDayApi.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/WorkingDayApi.java new file mode 100644 index 0000000..7939be8 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/api/WorkingDayApi.java @@ -0,0 +1,37 @@ +package com.ikon.projectmanagement.api; + +import com.ikon.projectmanagement.dto.response.WorkingDaysResponseDto; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Working Day APIs", description = "APIs for managing local working days") +@RequestMapping("/api/v1/working-days") +public interface WorkingDayApi { + + @Operation(summary = "Get all working days configurations", description = "Fetches all working days configurations.", responses = { + @ApiResponse(responseCode = "200", description = "Working days list fetched successfully"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping + ResponseEntity> getAllWorkingDays( + @RequestHeader("Authorization") String accessToken, + Pageable pageable); + + @Operation(summary = "Get working days by year", description = "Fetches working days configuration by year.", responses = { + @ApiResponse(responseCode = "200", description = "Working days fetched successfully"), + @ApiResponse(responseCode = "404", description = "Working days not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/{year}") + ResponseEntity getWorkingDaysByYear( + @RequestHeader("Authorization") String accessToken, + @PathVariable String year); +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ActionItemDTO.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ActionItemDTO.java new file mode 100644 index 0000000..1e0c396 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ActionItemDTO.java @@ -0,0 +1,17 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ActionItemDTO { + + private String item; + + private String responsible; + + private String dueDate; +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ActionsWrapperDTO.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ActionsWrapperDTO.java new file mode 100644 index 0000000..16db358 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ActionsWrapperDTO.java @@ -0,0 +1,15 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ActionsWrapperDTO { + + private List actions; +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/AgendaItemDTO.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/AgendaItemDTO.java new file mode 100644 index 0000000..c28baf8 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/AgendaItemDTO.java @@ -0,0 +1,15 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AgendaItemDTO { + + private String item; + + private String owner; +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/AgendaWrapperDTO.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/AgendaWrapperDTO.java new file mode 100644 index 0000000..f1da920 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/AgendaWrapperDTO.java @@ -0,0 +1,15 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AgendaWrapperDTO { + + private List agenda; +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/AttendeeDTO.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/AttendeeDTO.java new file mode 100644 index 0000000..3135f89 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/AttendeeDTO.java @@ -0,0 +1,17 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AttendeeDTO { + + private String name; + + private String role; + + private String contact; +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/AttendeesWrapperDTO.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/AttendeesWrapperDTO.java new file mode 100644 index 0000000..0b047b2 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/AttendeesWrapperDTO.java @@ -0,0 +1,15 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AttendeesWrapperDTO { + + private List attendees; +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/DecisionsWrapperDTO.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/DecisionsWrapperDTO.java new file mode 100644 index 0000000..90c84ce --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/DecisionsWrapperDTO.java @@ -0,0 +1,15 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DecisionsWrapperDTO { + + private List decisions; +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/EmployeeRequestDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/EmployeeRequestDto.java new file mode 100644 index 0000000..37073bf --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/EmployeeRequestDto.java @@ -0,0 +1,20 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class EmployeeRequestDto { + private String empId; + private String name; + private String email; + private String organizationEmail; + private String role; + private String grade; + private Boolean active; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/FxRateDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/FxRateDto.java new file mode 100644 index 0000000..dc0601d --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/FxRateDto.java @@ -0,0 +1,19 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class FxRateDto { + private String fromCurrency; + private String toCurrency; + private BigDecimal rate; + private String effectiveDate; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/GradeRequestDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/GradeRequestDto.java new file mode 100644 index 0000000..bd6a2b1 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/GradeRequestDto.java @@ -0,0 +1,18 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class GradeRequestDto { + private UUID id; + private UUID accountId; + private String grade; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ImportDataRequestDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ImportDataRequestDto.java new file mode 100644 index 0000000..1f87bc2 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ImportDataRequestDto.java @@ -0,0 +1,17 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ImportDataRequestDto { + private Integer page; + private Integer size; + private String filterBy; + private String filterValue; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/IssueCreateRequestDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/IssueCreateRequestDto.java new file mode 100644 index 0000000..8b64219 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/IssueCreateRequestDto.java @@ -0,0 +1,47 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class IssueCreateRequestDto { + + private String issueTitle; + + private Integer issueProbability; + + private BigDecimal grossIssueValue; + + private BigDecimal probableIssueValue; + + private BigDecimal probableIssueValueInUSD; + + private String issueImpact; + + private String issueOwner; + + private String issueDescription; + + private String mitigationAction; + + private UUID projectIdentifier; + + private Boolean financialIssue; + + private LocalDateTime issueCreatedDate; + + private String issueOptionsSelectId; + + private String issueStatus; + + private Integer issueAge; + + private String effectedSprintId; +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/MeetingDetailsDTO.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/MeetingDetailsDTO.java new file mode 100644 index 0000000..7886ea6 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/MeetingDetailsDTO.java @@ -0,0 +1,27 @@ +package com.ikon.projectmanagement.dto.request; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class MeetingDetailsDTO { + + private String title; + + private String projectName; + + private String place; + + private String date; + + private String time; + + private String duration; + + private String calledBy; +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/MeetingRequestDTO.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/MeetingRequestDTO.java new file mode 100644 index 0000000..cb35278 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/MeetingRequestDTO.java @@ -0,0 +1,30 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +import lombok.AllArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class MeetingRequestDTO { + + private MeetingDetailsDTO meeting; + + private AttendeesWrapperDTO attendees; + + private AgendaWrapperDTO agenda; + + private DecisionsWrapperDTO decisions; + + private ActionsWrapperDTO actions; + + private OthersDTO others; + + private UUID projectIdentifier; + + private String status; +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/OthersDTO.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/OthersDTO.java new file mode 100644 index 0000000..4dc9abc --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/OthersDTO.java @@ -0,0 +1,13 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class OthersDTO { + + private String notes; +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ProductExpenseRequestDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ProductExpenseRequestDto.java new file mode 100644 index 0000000..67afcd5 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ProductExpenseRequestDto.java @@ -0,0 +1,22 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProductExpenseRequestDto { + + private String expenseName; + private String location; + private String currency; + private Double cost; + private Integer quantity; + private Double totalCost; + private String remarks; + +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ProductOfProjectRequestDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ProductOfProjectRequestDto.java new file mode 100644 index 0000000..0328cfe --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ProductOfProjectRequestDto.java @@ -0,0 +1,35 @@ +package com.ikon.projectmanagement.dto.request; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import com.ikon.projectmanagement.dto.response.ProductExpenseResponseDto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProductOfProjectRequestDto { + + private UUID projectIdentifier; + private String projectName; + private UUID projectManager; + + private UUID accountId; + private UUID leadIdentifier; + + private String productStatus; + private String projectStatus; + private String productType; + private String productDescription; + private Double discountPercent; + + private Map expenseDetails; + private List resourceDataWithAllocation; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ProductPSResourceAllocationRequestDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ProductPSResourceAllocationRequestDto.java new file mode 100644 index 0000000..817785c --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ProductPSResourceAllocationRequestDto.java @@ -0,0 +1,25 @@ +package com.ikon.projectmanagement.dto.request; + +import java.util.Map; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProductPSResourceAllocationRequestDto { + + private String resourceType; + private String role; + private Integer gradeId; + private String employeeName; + private String taskName; + private String resourceId; + private Long taskId; + private Map allocation; + private Map> detailedAllocation; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ProjectRequestDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ProjectRequestDto.java new file mode 100644 index 0000000..c3ea76d --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ProjectRequestDto.java @@ -0,0 +1,51 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.*; +import java.time.LocalDate; +import java.util.*; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProjectRequestDto { + + private UUID projectManager; + private String projectName; + private String projectStatus; + private String projectNumber; + private String parentProjectNo; + private String contractNumber; + private String projectClient; + private String projectCity; + private String projectCountry; + private String currency; + private String projectImage; + private String contractUpload; + private String source; + private String productType; + private String expenses; + private String formattedActualRevenueIncludingVAT_deal; + private Boolean isCompleted; + private Boolean groupNotExist; + private Boolean isDebtRevenue_deal; + private String projectDescription; + + private UUID updatedBy; + + private UUID projectManagerDelegates; + + private LocalDate projectStartDate; + private LocalDate contractedStartDate; + private LocalDate contractedEndDate; + + private List projectTeam; + private List projectTeamUnderProjectManager; + private List projectTeamUnderProjectManagerDelegates; + + private String groupAssigneesEditStr; + private String groupAssigneesViewStr; + + private Map participants; + private Map contractedProductIdentifierWiseDataObj; +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/RiskCreateRequestDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/RiskCreateRequestDto.java new file mode 100644 index 0000000..9ad0e30 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/RiskCreateRequestDto.java @@ -0,0 +1,45 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RiskCreateRequestDto { + + private String riskTitle; + + private Integer riskProbability; + + private BigDecimal grossRiskValue; + + private BigDecimal probableRiskValue; + + private BigDecimal probableRiskValueInUSD; + + private String riskImpact; + + private String riskOwner; + + private String riskDescription; + + private UUID projectIdentifier; + + private Boolean financialRisk; + + private LocalDateTime riskCreatedDate; + + private String riskOptionsSelectId; + + private String riskStatus; + + private Integer riskAge; + + private String effectedSprintId; +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/RoleRequestDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/RoleRequestDto.java new file mode 100644 index 0000000..9685c8c --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/RoleRequestDto.java @@ -0,0 +1,17 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class RoleRequestDto { + private UUID id; + private String role; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ScheduleDependencyDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ScheduleDependencyDto.java new file mode 100644 index 0000000..b3c892f --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ScheduleDependencyDto.java @@ -0,0 +1,14 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ScheduleDependencyDto { + private Long id; + private Long predecessorId; + private Integer dependencyType; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ScheduleGroupDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ScheduleGroupDto.java new file mode 100644 index 0000000..3bdff35 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ScheduleGroupDto.java @@ -0,0 +1,18 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ScheduleGroupDto { + private String id; + private String groupName; + private List taskIds = new ArrayList<>(); + private String color; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ScheduleRequestDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ScheduleRequestDto.java new file mode 100644 index 0000000..fd1f51f --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ScheduleRequestDto.java @@ -0,0 +1,17 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ScheduleRequestDto { + private List task; + private List dependency; + private Map group; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ScheduleTaskDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ScheduleTaskDto.java new file mode 100644 index 0000000..9e3207c --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/ScheduleTaskDto.java @@ -0,0 +1,23 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ScheduleTaskDto { + private Long id; + private Long parentId; + private String taskName; + private Double taskDuration; + private String taskPredecessor; + private Integer dependencyType; + private String taskColour; + private Double delayDuration; + private String taskDescription; + private String taskStart; + private String taskEnd; + private Boolean milestoneTask; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/WorkflowTransitionRequestDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/WorkflowTransitionRequestDto.java new file mode 100644 index 0000000..e22966d --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/WorkflowTransitionRequestDto.java @@ -0,0 +1,22 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WorkflowTransitionRequestDto { + + /** + * The target productStatus string to transition to. + * e.g. "Schedule Submitted From Product", + * "Submitted Resources and Expenses For Product", + * "Closed", + * "Recall Product Created from Schedule", etc. + */ + private String targetStatus; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/WorkingDayDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/WorkingDayDto.java new file mode 100644 index 0000000..ec8b849 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/request/WorkingDayDto.java @@ -0,0 +1,16 @@ +package com.ikon.projectmanagement.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class WorkingDayDto { + private String year; + private String month; + private String workingDays; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/DashboardWidgetsResponseDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/DashboardWidgetsResponseDto.java new file mode 100644 index 0000000..400335b --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/DashboardWidgetsResponseDto.java @@ -0,0 +1,16 @@ +package com.ikon.projectmanagement.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class DashboardWidgetsResponseDto { + private Long totalProjects; + private Long totalIssues; + private Long totalRisksCount; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/EmployeeResponseDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/EmployeeResponseDto.java new file mode 100644 index 0000000..fb52983 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/EmployeeResponseDto.java @@ -0,0 +1,26 @@ +package com.ikon.projectmanagement.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class EmployeeResponseDto { + private String empId; + private UUID accountId; + private String name; + private String email; + private String organizationEmail; + private String role; + private String grade; + private Boolean active; + private String highestQualification; + private String joiningDate; + private String confirmationDate; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/FxRateDetailsDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/FxRateDetailsDto.java new file mode 100644 index 0000000..f1c2c33 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/FxRateDetailsDto.java @@ -0,0 +1,12 @@ +package com.ikon.projectmanagement.dto.response; + +import lombok.Data; + +@Data +public class FxRateDetailsDto { + private String id; + private String currency; + private Double fxRate; + private Boolean activeStatus; + private String year; +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/FxRateDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/FxRateDto.java new file mode 100644 index 0000000..f945662 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/FxRateDto.java @@ -0,0 +1,22 @@ +package com.ikon.projectmanagement.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.util.UUID; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class FxRateDto { + private UUID id; + private UUID accountId; + private String fromCurrency; + private String toCurrency; + private BigDecimal rate; + private String effectiveDate; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/FxRateResponseDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/FxRateResponseDto.java new file mode 100644 index 0000000..9f147c6 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/FxRateResponseDto.java @@ -0,0 +1,13 @@ +package com.ikon.projectmanagement.dto.response; + +import java.util.Map; +import java.util.UUID; + +import lombok.Data; + +@Data +public class FxRateResponseDto { + private UUID fxRateId; + private UUID accountIdentifier; + private Map> fxRateDetails; +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/GradeResponseDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/GradeResponseDto.java new file mode 100644 index 0000000..da20156 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/GradeResponseDto.java @@ -0,0 +1,18 @@ +package com.ikon.projectmanagement.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class GradeResponseDto { + private UUID id; + private UUID accountId; + private String grade; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/IssueResponseDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/IssueResponseDto.java new file mode 100644 index 0000000..2ca4686 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/IssueResponseDto.java @@ -0,0 +1,49 @@ +package com.ikon.projectmanagement.dto.response; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class IssueResponseDto { + + private UUID issueId; + + private String issueTitle; + + private Integer issueProbability; + + private BigDecimal grossIssueValue; + + private BigDecimal probableIssueValue; + + private BigDecimal probableIssueValueInUSD; + + private String issueImpact; + + private String issueOwner; + + private String issueDescription; + + private String mitigationAction; + + private UUID projectIdentifier; + + private Boolean financialIssue; + + private LocalDateTime issueCreatedDate; + + private String issueOptionsSelectId; + + private String issueStatus; + + private Integer issueAge; + + private String effectedSprintId; +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/MeetingResponseDTO.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/MeetingResponseDTO.java new file mode 100644 index 0000000..d4ff1af --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/MeetingResponseDTO.java @@ -0,0 +1,37 @@ +package com.ikon.projectmanagement.dto.response; + +import com.ikon.projectmanagement.dto.request.*; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class MeetingResponseDTO { + + private UUID id; + + private MeetingDetailsDTO meeting; + + private AttendeesWrapperDTO attendees; + + private AgendaWrapperDTO agenda; + + private DecisionsWrapperDTO decisions; + + private ActionsWrapperDTO actions; + + private OthersDTO others; + + private UUID projectIdentifier; + + private String status; + + private LocalDateTime createdAt; + + private LocalDateTime updatedAt; +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ProductExpenseResponseDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ProductExpenseResponseDto.java new file mode 100644 index 0000000..2c02592 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ProductExpenseResponseDto.java @@ -0,0 +1,25 @@ +package com.ikon.projectmanagement.dto.response; + +import java.util.UUID; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data + +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProductExpenseResponseDto { + private UUID expenseIdentifier; + private String expenseName; + private String location; + private String currency; + private Double cost; + private Integer quantity; + private Double totalCost; + private String remarks; + +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ProductOfProjectResponseDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ProductOfProjectResponseDto.java new file mode 100644 index 0000000..899e475 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ProductOfProjectResponseDto.java @@ -0,0 +1,40 @@ +package com.ikon.projectmanagement.dto.response; + +import java.util.Map; +import java.util.UUID; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProductOfProjectResponseDto { + + private UUID productIdentifier; + + private UUID projectIdentifier; + private String projectName; + private UUID projectManager; + + private UUID accountId; + private UUID leadIdentifier; + + private String productStatus; + private String projectStatus; + private String productType; + private String productDescription; + private Double discountPercent; + + private String createdOn; + private UUID createdBy; + private UUID updatedBy; + private String updatedOn; + + private Map expenseDetails; + + private ScheduleResponseDto scheduleData; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ProductPSResourceAllocationResponseDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ProductPSResourceAllocationResponseDto.java new file mode 100644 index 0000000..f333e54 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ProductPSResourceAllocationResponseDto.java @@ -0,0 +1,27 @@ +package com.ikon.projectmanagement.dto.response; + +import java.util.Map; +import java.util.UUID; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProductPSResourceAllocationResponseDto { + + private UUID id; + private String resourceType; + private String role; + private Integer gradeId; + private String employeeName; + private String taskName; + private String resourceId; + private Long taskId; + private Map allocation; + private Map> detailedAllocation; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ProjectResponseDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ProjectResponseDto.java new file mode 100644 index 0000000..b252d9f --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ProjectResponseDto.java @@ -0,0 +1,63 @@ +package com.ikon.projectmanagement.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProjectResponseDto { + + private UUID projectIdentifier; + + private UUID projectManager; + private String projectName; + private String projectStatus; + private String projectNumber; + private String parentProjectNo; + private String contractNumber; + private String projectClient; + private String projectCity; + private String projectCountry; + private String currency; + private String projectImage; + private String contractUpload; + private String source; + private String productType; + private String expenses; + private String formattedActualRevenueIncludingVAT_deal; + private Boolean isCompleted; + private Boolean groupNotExist; + private Boolean isDebtRevenue_deal; + private String projectDescription; + + private UUID createdById; + private UUID updatedBy; + private UUID projectManagerDelegates; + + private LocalDate projectStartDate; + private LocalDate contractedStartDate; + private LocalDate contractedEndDate; + private LocalDate createdOn; + private LocalDate updatedOn; + + private List projectTeam; + private List projectTeamUnderProjectManager; + private List projectTeamUnderProjectManagerDelegates; + + private String groupAssigneesEditStr; + private String groupAssigneesViewStr; + + private Map participants; + private Map contractedProductIdentifierWiseDataObj; + + private UUID productIdentifier; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ProjectTimelineResponseDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ProjectTimelineResponseDto.java new file mode 100644 index 0000000..cea69f9 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ProjectTimelineResponseDto.java @@ -0,0 +1,19 @@ +package com.ikon.projectmanagement.dto.response; + +import java.util.UUID; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ProjectTimelineResponseDto { + private UUID projectIdentifier; + private String projectName; + private String startDate; + private String endDate; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/RiskResponseDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/RiskResponseDto.java new file mode 100644 index 0000000..0cd43b3 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/RiskResponseDto.java @@ -0,0 +1,46 @@ +package com.ikon.projectmanagement.dto.response; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RiskResponseDto { + private UUID riskIdentifier; + + private String riskTitle; + + private Integer riskProbability; + + private BigDecimal grossRiskValue; + + private BigDecimal probableRiskValue; + + private BigDecimal probableRiskValueInUSD; + + private String riskImpact; + + private String riskOwner; + + private String riskDescription; + + private UUID projectIdentifier; + + private Boolean financialRisk; + + private LocalDateTime riskCreatedDate; + + private String riskOptionsSelectId; + + private String riskStatus; + + private Integer riskAge; + + private String effectedSprintId; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/RoleResponseDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/RoleResponseDto.java new file mode 100644 index 0000000..9ac7edd --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/RoleResponseDto.java @@ -0,0 +1,18 @@ +package com.ikon.projectmanagement.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class RoleResponseDto { + private UUID id; + private UUID accountId; + private String role; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ScheduleResponseDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ScheduleResponseDto.java new file mode 100644 index 0000000..c58dea8 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/ScheduleResponseDto.java @@ -0,0 +1,23 @@ +package com.ikon.projectmanagement.dto.response; + +import com.ikon.projectmanagement.dto.request.ScheduleDependencyDto; +import com.ikon.projectmanagement.dto.request.ScheduleGroupDto; +import com.ikon.projectmanagement.dto.request.ScheduleTaskDto; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ScheduleResponseDto { + private UUID projectIdentifier; + private UUID productIdentifier; + private List task; + private List dependency; + private Map group; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/StatusWiseProjectResponseData.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/StatusWiseProjectResponseData.java new file mode 100644 index 0000000..6a0355a --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/StatusWiseProjectResponseData.java @@ -0,0 +1,15 @@ +package com.ikon.projectmanagement.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class StatusWiseProjectResponseData { + private String status; + private Long projectCount; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/WorkingDayDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/WorkingDayDto.java new file mode 100644 index 0000000..5f408ba --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/WorkingDayDto.java @@ -0,0 +1,20 @@ +package com.ikon.projectmanagement.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class WorkingDayDto { + private UUID id; + private UUID accountId; + private String year; + private String month; + private String workingDays; +} diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/WorkingDaysMonthDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/WorkingDaysMonthDto.java new file mode 100644 index 0000000..f3f4702 --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/WorkingDaysMonthDto.java @@ -0,0 +1,12 @@ +package com.ikon.projectmanagement.dto.response; + +import lombok.Data; + +@Data +public class WorkingDaysMonthDto { + + private String year; + private String month; + private Integer workingDays; + +} \ No newline at end of file diff --git a/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/WorkingDaysResponseDto.java b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/WorkingDaysResponseDto.java new file mode 100644 index 0000000..6431acc --- /dev/null +++ b/backend/projectmanagement-client/src/main/java/com/ikon/projectmanagement/dto/response/WorkingDaysResponseDto.java @@ -0,0 +1,12 @@ +package com.ikon.projectmanagement.dto.response; + +import lombok.Data; +import java.util.Map; +import java.util.UUID; + +@Data +public class WorkingDaysResponseDto { + private UUID workingId; + private UUID accountIdentifier; + private Map> workingDaysDetails; +} \ No newline at end of file diff --git a/backend/projectmanagement-server/ikoncloud-dev.keross.com.crt b/backend/projectmanagement-server/ikoncloud-dev.keross.com.crt new file mode 100644 index 0000000..dab5883 Binary files /dev/null and b/backend/projectmanagement-server/ikoncloud-dev.keross.com.crt differ diff --git a/backend/projectmanagement-server/pom.xml b/backend/projectmanagement-server/pom.xml new file mode 100644 index 0000000..a7169fe --- /dev/null +++ b/backend/projectmanagement-server/pom.xml @@ -0,0 +1,128 @@ + + + 4.0.0 + + com.ikon.projectmanagement + projectmanagement + 1.0.0 + + projectmanagement-server + + + confluent + https://packages.confluent.io/maven/ + + + + + com.ikon + ikon-sdk + ${ikon.sdk.version} + + + com.ikon.projectmanagement + projectmanagement-client + ${projectmanagement.version} + + + com.ikon + ikon-client + ${ikon.sdk.version} + + + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + org.modelmapper + modelmapper + 3.2.2 + + + + com.mysql + mysql-connector-j + runtime + + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + org.springframework.kafka + spring-kafka + + + io.confluent + kafka-avro-serializer + ${avro-serializer.version} + + + + com.ikon + ikon-connector + 1.0.0 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + + + \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/ProjectManagementApplication.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/ProjectManagementApplication.java new file mode 100644 index 0000000..260d828 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/ProjectManagementApplication.java @@ -0,0 +1,42 @@ + +package com.ikon.projectmanagement; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.actuate.autoconfigure.cassandra.CassandraHealthContributorAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +import com.ikon.autoconfigure.annotation.EnableIkonSdk; +import com.ikon.sdk.config.IkonSdkConfig; + +@EntityScan(basePackages = { + "com.ikon.projectmanagement.entity", + +}) +@EnableJpaRepositories(basePackages = { + "com.ikon.projectmanagement.repository", + +}) +@EnableFeignClients(basePackages = { + "com.ikon.client", + +}) +@SpringBootApplication(scanBasePackages = { + "com.ikon.projectmanagement", + +}, exclude = { + CassandraAutoConfiguration.class, + CassandraHealthContributorAutoConfiguration.class +}) +@EnableJpaAuditing +@EnableIkonSdk(configuration = IkonSdkConfig.class) +public class ProjectManagementApplication { + + public static void main(String[] args) { + SpringApplication.run(ProjectManagementApplication.class, args); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/config/ModelMapperConfig.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/config/ModelMapperConfig.java new file mode 100644 index 0000000..1135d7e --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/config/ModelMapperConfig.java @@ -0,0 +1,15 @@ +package com.ikon.projectmanagement.config; + +import org.modelmapper.ModelMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ModelMapperConfig { + + @Bean + ModelMapper modelMapper() { + return new ModelMapper(); + } + +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/config/RestTemplateConfigs.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/config/RestTemplateConfigs.java new file mode 100644 index 0000000..9cfa566 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/config/RestTemplateConfigs.java @@ -0,0 +1,13 @@ +package com.ikon.projectmanagement.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfigs { + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/config/SecurityConfig.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/config/SecurityConfig.java new file mode 100644 index 0000000..481272d --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/config/SecurityConfig.java @@ -0,0 +1,67 @@ +package com.ikon.projectmanagement.config; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import com.ikon.webservice.security.IkonJwtTokenConverter; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity +public class SecurityConfig { + + private Converter ikonJwtTokenConverter = new IkonJwtTokenConverter( + new JwtGrantedAuthoritiesConverter()); + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .cors(cors -> cors.configurationSource(corsConfigurationSource())) // Enable CORS + .csrf(AbstractHttpConfigurer::disable) + .httpBasic(AbstractHttpConfigurer::disable) + .authorizeHttpRequests((authorize) -> { + authorize + + .requestMatchers("/actuator/health", "/actuator/health/**").permitAll() + + .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() + .anyRequest().authenticated(); + }) + .oauth2ResourceServer((oauth2) -> { + oauth2.jwt((jwt) -> jwt.jwtAuthenticationConverter(ikonJwtTokenConverter)); + }); + + return http.build(); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(List.of("http://localhost:3000")); + configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); + configuration.setAllowedHeaders(List.of("*")); + configuration.setAllowCredentials(true); + configuration.setMaxAge(3600L); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } + +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/DashboardController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/DashboardController.java new file mode 100644 index 0000000..4dde71d --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/DashboardController.java @@ -0,0 +1,31 @@ +package com.ikon.projectmanagement.controller; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; +import com.ikon.projectmanagement.api.DashboardApi; +import com.ikon.projectmanagement.dto.response.DashboardWidgetsResponseDto; +import com.ikon.projectmanagement.dto.response.StatusWiseProjectResponseData; +import com.ikon.projectmanagement.service.DashboardService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@RestController +@RequiredArgsConstructor +@Slf4j +public class DashboardController implements DashboardApi { + private final DashboardService dashboardService; + + @Override + public ResponseEntity getWidgetsData(String accessToken) { + DashboardWidgetsResponseDto widgetsData = dashboardService.getDashboardWidgetsData(); + return ResponseEntity.ok(widgetsData); + } + + @Override + public ResponseEntity> getStatusWiseProjectData(String accessToken) { + List statusWiseData = dashboardService.getStatusWiseProjectData(); + return ResponseEntity.ok(statusWiseData); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/EmployeeController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/EmployeeController.java new file mode 100644 index 0000000..d696320 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/EmployeeController.java @@ -0,0 +1,63 @@ +package com.ikon.projectmanagement.controller; + +import com.ikon.projectmanagement.api.EmployeeApi; +import com.ikon.projectmanagement.dto.request.EmployeeRequestDto; +import com.ikon.projectmanagement.dto.response.EmployeeResponseDto; +import com.ikon.projectmanagement.service.EmployeeService; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class EmployeeController implements EmployeeApi { + + private final EmployeeService employeeService; + + @Override + public ResponseEntity> getAllEmployees() { + return ResponseEntity.ok(employeeService.getAllEmployees()); + } + + @Override + public ResponseEntity getEmployeeById(String id) { + try { + EmployeeResponseDto employee = employeeService.getEmployeeById(id); + return ResponseEntity.ok(employee); + } catch (RuntimeException e) { + return ResponseEntity.notFound().build(); + } + } + + @Override + public ResponseEntity createEmployee(EmployeeRequestDto dto) { + try { + EmployeeResponseDto createdEmployee = employeeService.createEmployee(dto); + return ResponseEntity.status(HttpStatus.CREATED).body(createdEmployee); + } catch (RuntimeException e) { + return ResponseEntity.badRequest().build(); + } + } + + @Override + public ResponseEntity updateEmployee(String id, EmployeeRequestDto dto) { + try { + EmployeeResponseDto updatedEmployee = employeeService.updateEmployee(id, dto); + return ResponseEntity.ok(updatedEmployee); + } catch (RuntimeException e) { + return ResponseEntity.notFound().build(); + } + } + + @Override + public ResponseEntity deleteEmployee(String id) { + try { + employeeService.deleteEmployee(id); + return ResponseEntity.noContent().build(); + } catch (RuntimeException e) { + return ResponseEntity.notFound().build(); + } + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/FxRateController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/FxRateController.java new file mode 100644 index 0000000..b1db4c2 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/FxRateController.java @@ -0,0 +1,32 @@ +package com.ikon.projectmanagement.controller; + +import lombok.RequiredArgsConstructor; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import com.ikon.projectmanagement.api.FxRateApi; +import com.ikon.projectmanagement.dto.response.FxRateResponseDto; +import com.ikon.projectmanagement.service.FxRateService; + +@RestController +@RequiredArgsConstructor +public class FxRateController implements FxRateApi { + + private final FxRateService fxRateService; + + @Override + public ResponseEntity> getAllFxRates( + @RequestHeader("Authorization") String accessToken, + Pageable pageable) { + return ResponseEntity.ok(fxRateService.getAllFxRates(pageable)); + } + + public ResponseEntity getFxRateByYear( + @RequestHeader("Authorization") String accessToken, + @PathVariable String year) { + return ResponseEntity.ok(fxRateService.getFxRateByYear(year)); + } +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/GradeController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/GradeController.java new file mode 100644 index 0000000..ef659c1 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/GradeController.java @@ -0,0 +1,64 @@ +package com.ikon.projectmanagement.controller; + +import com.ikon.projectmanagement.api.GradeApi; +import com.ikon.projectmanagement.dto.request.GradeRequestDto; +import com.ikon.projectmanagement.dto.response.GradeResponseDto; +import com.ikon.projectmanagement.service.GradeService; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class GradeController implements GradeApi { + + private final GradeService gradeService; + + @Override + public ResponseEntity> getAllGrades() { + return ResponseEntity.ok(gradeService.getAllGrades()); + } + + @Override + public ResponseEntity getGradeById(UUID id) { + try { + GradeResponseDto grade = gradeService.getGradeById(id); + return ResponseEntity.ok(grade); + } catch (RuntimeException e) { + return ResponseEntity.notFound().build(); + } + } + + @Override + public ResponseEntity createGrade(GradeRequestDto dto) { + try { + GradeResponseDto createdGrade = gradeService.createGrade(dto); + return ResponseEntity.status(HttpStatus.CREATED).body(createdGrade); + } catch (RuntimeException e) { + return ResponseEntity.badRequest().build(); + } + } + + @Override + public ResponseEntity updateGrade(UUID id, GradeRequestDto dto) { + try { + GradeResponseDto updatedGrade = gradeService.updateGrade(id, dto); + return ResponseEntity.ok(updatedGrade); + } catch (RuntimeException e) { + return ResponseEntity.notFound().build(); + } + } + + @Override + public ResponseEntity deleteGrade(UUID id) { + try { + gradeService.deleteGrade(id); + return ResponseEntity.noContent().build(); + } catch (RuntimeException e) { + return ResponseEntity.notFound().build(); + } + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ImportEmployeeController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ImportEmployeeController.java new file mode 100644 index 0000000..d65be2c --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ImportEmployeeController.java @@ -0,0 +1,26 @@ +package com.ikon.projectmanagement.controller; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import com.ikon.projectmanagement.api.ImportEmployeeApi; +import com.ikon.projectmanagement.dto.response.EmployeeResponseDto; +import com.ikon.projectmanagement.service.ImportEmployeeService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@RestController +@RequiredArgsConstructor +@Slf4j +public class ImportEmployeeController implements ImportEmployeeApi { + + private final ImportEmployeeService importEmployeeService; + + @Override + public ResponseEntity> fetchAllEmployees(String accessToken) { + return ResponseEntity.ok(importEmployeeService.importEmployees(accessToken)); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ImportFxRateController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ImportFxRateController.java new file mode 100644 index 0000000..b4b2883 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ImportFxRateController.java @@ -0,0 +1,26 @@ +package com.ikon.projectmanagement.controller; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import com.ikon.projectmanagement.api.ImportFxRateApi; +import com.ikon.projectmanagement.dto.response.FxRateDto; +import com.ikon.projectmanagement.service.ImportFxRateService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@RestController +@RequiredArgsConstructor +@Slf4j +public class ImportFxRateController implements ImportFxRateApi { + + private final ImportFxRateService importFxRateService; + + @Override + public ResponseEntity> fetchAllFxRates(String accessToken) { + return ResponseEntity.ok(importFxRateService.importFxRates(accessToken)); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ImportGradeController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ImportGradeController.java new file mode 100644 index 0000000..47d431d --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ImportGradeController.java @@ -0,0 +1,26 @@ +package com.ikon.projectmanagement.controller; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import com.ikon.projectmanagement.api.ImportGradeApi; +import com.ikon.projectmanagement.dto.response.GradeResponseDto; +import com.ikon.projectmanagement.service.ImportGradeService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@RestController +@RequiredArgsConstructor +@Slf4j +public class ImportGradeController implements ImportGradeApi { + + private final ImportGradeService importGradeService; + + @Override + public ResponseEntity> fetchAllGrades(String accessToken) { + return ResponseEntity.ok(importGradeService.importGrades(accessToken)); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ImportRoleController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ImportRoleController.java new file mode 100644 index 0000000..8078538 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ImportRoleController.java @@ -0,0 +1,26 @@ +package com.ikon.projectmanagement.controller; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import com.ikon.projectmanagement.api.ImportRoleApi; +import com.ikon.projectmanagement.dto.response.RoleResponseDto; +import com.ikon.projectmanagement.service.ImportRoleService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@RestController +@RequiredArgsConstructor +@Slf4j +public class ImportRoleController implements ImportRoleApi { + + private final ImportRoleService importRoleService; + + @Override + public ResponseEntity> fetchAllRoles(String accessToken) { + return ResponseEntity.ok(importRoleService.importRoles(accessToken)); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ImportWorkingDayController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ImportWorkingDayController.java new file mode 100644 index 0000000..13658c2 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ImportWorkingDayController.java @@ -0,0 +1,26 @@ +package com.ikon.projectmanagement.controller; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import com.ikon.projectmanagement.api.ImportWorkingDayApi; +import com.ikon.projectmanagement.dto.response.WorkingDayDto; +import com.ikon.projectmanagement.service.ImportWorkingDayService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@RestController +@RequiredArgsConstructor +@Slf4j +public class ImportWorkingDayController implements ImportWorkingDayApi { + + private final ImportWorkingDayService importWorkingDayService; + + @Override + public ResponseEntity> fetchAllWorkingDays(String accessToken) { + return ResponseEntity.ok(importWorkingDayService.importWorkingDays(accessToken)); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/IssueController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/IssueController.java new file mode 100644 index 0000000..388912b --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/IssueController.java @@ -0,0 +1,78 @@ +package com.ikon.projectmanagement.controller; + +import com.ikon.projectmanagement.api.IssueApi; +import com.ikon.projectmanagement.dto.request.IssueCreateRequestDto; +import com.ikon.projectmanagement.dto.response.IssueResponseDto; +import com.ikon.projectmanagement.service.IssueService; + +import java.util.List; +import java.util.UUID; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class IssueController implements IssueApi { + + private final IssueService issueService; + + @Override + public ResponseEntity createIssue( + String accessToken, + IssueCreateRequestDto issueCreateRequestDto) { + + IssueResponseDto createdIssue = issueService.createIssue(issueCreateRequestDto); + + return ResponseEntity + .status(HttpStatus.CREATED) + .body(createdIssue); + } + + @Override + public ResponseEntity> getAllIssues( + String accessToken,UUID projectIdentifier) { + + return ResponseEntity.ok( + issueService.getAllIssues(projectIdentifier)); + } + + @Override + public ResponseEntity getIssueById( + String accessToken, + UUID issueId) { + + return ResponseEntity.ok( + issueService.getIssueById(issueId)); + } + + @Override + public ResponseEntity updateIssue( + String accessToken, + UUID issueId, + IssueCreateRequestDto issueCreateRequestDto) { + + return ResponseEntity.ok( + issueService.updateIssue(issueId, issueCreateRequestDto)); + } + + // @Override + // public ResponseEntity deleteIssue( + // String accessToken, + // UUID issueId) { + + // issueService.deleteIssue(issueId); + // return ResponseEntity.noContent().build(); + // } + + // @Override + // public ResponseEntity> getIssuesByProject( + // String accessToken, + // UUID projectIdentifier) { + + // return ResponseEntity.ok( + // issueService.getIssuesByProject(projectIdentifier)); + // } +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/MeetingController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/MeetingController.java new file mode 100644 index 0000000..22cb8b0 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/MeetingController.java @@ -0,0 +1,72 @@ +package com.ikon.projectmanagement.controller; + +import com.ikon.projectmanagement.api.MeetingApi; +import com.ikon.projectmanagement.dto.request.MeetingRequestDTO; +import com.ikon.projectmanagement.dto.response.MeetingResponseDTO; +import com.ikon.projectmanagement.service.MeetingService; + +import java.util.List; +import java.util.UUID; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class MeetingController implements MeetingApi { + + private final MeetingService meetingService; + + @Override + public ResponseEntity createMeeting( + String accessToken, + MeetingRequestDTO meetingRequestDTO) { + + MeetingResponseDTO createdMeeting = meetingService.createMeeting(meetingRequestDTO); + + return ResponseEntity + .status(HttpStatus.CREATED) + .body(createdMeeting); + } + + @Override + public ResponseEntity> getAllMeetings( + String accessToken, + UUID projectIdentifier) { + + return ResponseEntity.ok( + meetingService.getAllMeetings(projectIdentifier)); + } + + + + @Override + public ResponseEntity getMeetingById( + String accessToken, + UUID meetingId) { + + return ResponseEntity.ok( + meetingService.getMeetingById(meetingId)); + } + + @Override + public ResponseEntity updateMeeting( + String accessToken, + UUID meetingId, + MeetingRequestDTO meetingRequestDTO) { + + return ResponseEntity.ok( + meetingService.updateMeeting(meetingId, meetingRequestDTO)); + } + + @Override + public ResponseEntity deleteMeeting( + String accessToken, + UUID meetingId) { + + meetingService.deleteMeeting(meetingId); + return ResponseEntity.noContent().build(); + } +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ProductOfProjectController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ProductOfProjectController.java new file mode 100644 index 0000000..6fd1c80 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ProductOfProjectController.java @@ -0,0 +1,67 @@ +package com.ikon.projectmanagement.controller; + +import java.util.List; +import java.util.UUID; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import com.ikon.projectmanagement.api.ProductOfProjectApi; +import com.ikon.projectmanagement.dto.request.ProductOfProjectRequestDto; +import com.ikon.projectmanagement.dto.request.WorkflowTransitionRequestDto; +import com.ikon.projectmanagement.dto.response.ProductOfProjectResponseDto; +import com.ikon.projectmanagement.service.ProductOfProjectService; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +public class ProductOfProjectController implements ProductOfProjectApi { + + private final ProductOfProjectService productOfProjectService; + + @Override + public ResponseEntity> getAllProductsByProject( + String accessToken, + UUID projectIdentifier) { + + return ResponseEntity.ok( + productOfProjectService.getProductsByProject(projectIdentifier)); + } + + @Override + public ResponseEntity getProductById( + String accessToken, + UUID projectIdentifier, + UUID productIdentifier) { + + return ResponseEntity.ok( + productOfProjectService.getProduct(projectIdentifier, productIdentifier)); + } + + @Override + public ResponseEntity updateProduct( + String accessToken, + UUID projectIdentifier, + UUID productIdentifier, + ProductOfProjectRequestDto productDto) { + + return ResponseEntity.ok( + productOfProjectService.updateProduct(projectIdentifier, productIdentifier, + productDto)); + } + + @Override + public ResponseEntity transitionProductStatus( + String accessToken, + UUID projectIdentifier, + UUID productIdentifier, + WorkflowTransitionRequestDto transitionDto) { + + return ResponseEntity.ok( + productOfProjectService.transitionStatus( + projectIdentifier, + productIdentifier, + transitionDto.getTargetStatus())); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ProductResourceAllocationController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ProductResourceAllocationController.java new file mode 100644 index 0000000..07b2b12 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ProductResourceAllocationController.java @@ -0,0 +1,62 @@ +package com.ikon.projectmanagement.controller; + +import java.util.List; +import java.util.UUID; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import com.ikon.projectmanagement.api.ProductResourceAllocationApi; +import com.ikon.projectmanagement.dto.request.ProductPSResourceAllocationRequestDto; +import com.ikon.projectmanagement.dto.response.ProductPSResourceAllocationResponseDto; +import com.ikon.projectmanagement.service.ProductResourceAllocationService; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +public class ProductResourceAllocationController implements ProductResourceAllocationApi { + + private final ProductResourceAllocationService allocationService; + + @Override + public ResponseEntity> getResourceAllocations( + String accessToken, + UUID productIdentifier) { + return ResponseEntity.ok(allocationService.getResourceAllocations(productIdentifier)); + } + + @Override + public ResponseEntity getResourceAllocation( + String accessToken, + UUID productIdentifier, + UUID allocationIdentifier) { + return ResponseEntity.ok(allocationService.getResourceAllocation(productIdentifier, allocationIdentifier)); + } + + @Override + public ResponseEntity createResourceAllocation( + String accessToken, + UUID productIdentifier, + ProductPSResourceAllocationRequestDto allocationRequestDto) { + return ResponseEntity.status(201).body(allocationService.createResourceAllocation(productIdentifier, allocationRequestDto)); + } + + @Override + public ResponseEntity updateResourceAllocation( + String accessToken, + UUID productIdentifier, + UUID allocationIdentifier, + ProductPSResourceAllocationRequestDto allocationRequestDto) { + return ResponseEntity.ok(allocationService.updateResourceAllocation(productIdentifier, allocationIdentifier, allocationRequestDto)); + } + + @Override + public ResponseEntity deleteResourceAllocation( + String accessToken, + UUID productIdentifier, + UUID allocationIdentifier) { + allocationService.deleteResourceAllocation(productIdentifier, allocationIdentifier); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ProjectController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ProjectController.java new file mode 100644 index 0000000..44bec08 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ProjectController.java @@ -0,0 +1,66 @@ +package com.ikon.projectmanagement.controller; + +import com.ikon.projectmanagement.api.ProjectApi; +import com.ikon.projectmanagement.dto.request.ProjectRequestDto; +import com.ikon.projectmanagement.dto.response.ProjectResponseDto; +import com.ikon.projectmanagement.dto.response.ProjectTimelineResponseDto; +import com.ikon.projectmanagement.service.ProjectService; + +import java.util.List; +import java.util.UUID; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class ProjectController implements ProjectApi { + + private final ProjectService projectService; + + @Override + public ResponseEntity createProject( + String accessToken, + ProjectRequestDto projectRequestDto) { + + ProjectResponseDto createdProject = projectService.createProject(projectRequestDto); + + return ResponseEntity + .status(HttpStatus.CREATED) + .body(createdProject); + } + + @Override + public ResponseEntity> getAllProjects( + String accessToken) { + + return ResponseEntity.ok( + projectService.getAllProjects()); + } + + @Override + public ResponseEntity getProjectByProjectIdentifier(String accessToken, + UUID projectIdentifier) { + + return ResponseEntity.ok(projectService.getProjectByProjectIdentifier(projectIdentifier)); + } + + @Override + public ResponseEntity updateProject( + String accessToken, + UUID projectIdentifier, + ProjectRequestDto projectDto) { + + return ResponseEntity.ok( + projectService.updateProject(projectIdentifier, projectDto)); + } + + @Override + public ResponseEntity> getAllActiveProjectsTimeline( + String accessToken) { + + return ResponseEntity.ok(projectService.getAllActiveProjectsTimeline()); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/RiskController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/RiskController.java new file mode 100644 index 0000000..d60f659 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/RiskController.java @@ -0,0 +1,71 @@ +package com.ikon.projectmanagement.controller; + +import com.ikon.projectmanagement.api.RiskApi; +import com.ikon.projectmanagement.dto.request.RiskCreateRequestDto; +import com.ikon.projectmanagement.dto.response.RiskResponseDto; +import com.ikon.projectmanagement.service.RiskService; + +import java.util.List; +import java.util.UUID; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class RiskController implements RiskApi { + + private final RiskService riskService; + + @Override + public ResponseEntity createRisk( + String accessToken, + RiskCreateRequestDto riskCreateRequestDto) { + + RiskResponseDto createdRisk = riskService.createRisk(riskCreateRequestDto); + + return ResponseEntity + .status(HttpStatus.CREATED) + .body(createdRisk); + } + + @Override + public ResponseEntity> getAllRisks( + String accessToken) { + + return ResponseEntity.ok( + riskService.getAllRisks()); + } + + @Override + public ResponseEntity getRiskById( + String accessToken, + UUID riskId) { + + return ResponseEntity.ok( + riskService.getRiskById(riskId)); + } + + @Override + public ResponseEntity updateRisk( + String accessToken, + UUID riskId, + RiskCreateRequestDto riskCreateRequestDto) { + + return ResponseEntity.ok( + riskService.updateRisk(riskId, riskCreateRequestDto)); + } + + @Override + public ResponseEntity> getRisksByProject( + String accessToken, + String projectIdentifier) { + + return ResponseEntity.ok( + riskService.getRisksByProject(projectIdentifier)); + } + + +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/RoleController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/RoleController.java new file mode 100644 index 0000000..f9c2b1a --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/RoleController.java @@ -0,0 +1,64 @@ +package com.ikon.projectmanagement.controller; + +import com.ikon.projectmanagement.api.RoleApi; +import com.ikon.projectmanagement.dto.request.RoleRequestDto; +import com.ikon.projectmanagement.dto.response.RoleResponseDto; +import com.ikon.projectmanagement.service.RoleService; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class RoleController implements RoleApi { + + private final RoleService roleService; + + @Override + public ResponseEntity> getAllRoles() { + return ResponseEntity.ok(roleService.getAllRoles()); + } + + @Override + public ResponseEntity getRoleById(UUID id) { + try { + RoleResponseDto role = roleService.getRoleById(id); + return ResponseEntity.ok(role); + } catch (RuntimeException e) { + return ResponseEntity.notFound().build(); + } + } + + @Override + public ResponseEntity createRole(RoleRequestDto dto) { + try { + RoleResponseDto createdRole = roleService.createRole(dto); + return ResponseEntity.status(HttpStatus.CREATED).body(createdRole); + } catch (RuntimeException e) { + return ResponseEntity.badRequest().build(); + } + } + + @Override + public ResponseEntity updateRole(UUID id, RoleRequestDto dto) { + try { + RoleResponseDto updatedRole = roleService.updateRole(id, dto); + return ResponseEntity.ok(updatedRole); + } catch (RuntimeException e) { + return ResponseEntity.notFound().build(); + } + } + + @Override + public ResponseEntity deleteRole(UUID id) { + try { + roleService.deleteRole(id); + return ResponseEntity.noContent().build(); + } catch (RuntimeException e) { + return ResponseEntity.notFound().build(); + } + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ScheduleController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ScheduleController.java new file mode 100644 index 0000000..282f004 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/ScheduleController.java @@ -0,0 +1,39 @@ +package com.ikon.projectmanagement.controller; + +import com.ikon.projectmanagement.api.ScheduleApi; +import com.ikon.projectmanagement.dto.request.ScheduleRequestDto; +import com.ikon.projectmanagement.dto.response.ScheduleResponseDto; +import com.ikon.projectmanagement.service.ScheduleService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequiredArgsConstructor +public class ScheduleController implements ScheduleApi { + + private final ScheduleService scheduleService; + + @Override + public ResponseEntity saveSchedule( + String accessToken, + UUID projectIdentifier, + ScheduleRequestDto scheduleRequestDto) { + return ResponseEntity.ok(scheduleService.saveSchedule(projectIdentifier, scheduleRequestDto, accessToken)); + } + + @Override + public ResponseEntity getSchedule( + String accessToken, + UUID projectIdentifier) { + return ResponseEntity.ok(scheduleService.getSchedule(projectIdentifier, accessToken)); + } + + @Override + public ResponseEntity> getAllSchedules(String accessToken) { + return ResponseEntity.ok(scheduleService.getAllSchedules()); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/WorkingDayController.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/WorkingDayController.java new file mode 100644 index 0000000..a899d63 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/controller/WorkingDayController.java @@ -0,0 +1,36 @@ +package com.ikon.projectmanagement.controller; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; + +import com.ikon.projectmanagement.api.WorkingDayApi; +import com.ikon.projectmanagement.dto.response.WorkingDaysResponseDto; +import com.ikon.projectmanagement.service.WorkingDayService; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +public class WorkingDayController implements WorkingDayApi { + + private final WorkingDayService workingDayService; + + @Override + public ResponseEntity> getAllWorkingDays( + @RequestHeader("Authorization") String accessToken, Pageable pageable) { + + Page workingDaysList = workingDayService.getAllWorkingDays(pageable); + return ResponseEntity.ok(workingDaysList); + } + + @Override + public ResponseEntity getWorkingDaysByYear( + @RequestHeader("Authorization") String accessToken, @PathVariable String year) { + WorkingDaysResponseDto workingDays = workingDayService.getWorkingDaysByYear(year); + return ResponseEntity.ok(workingDays); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Employee.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Employee.java new file mode 100644 index 0000000..08685ab --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Employee.java @@ -0,0 +1,43 @@ +package com.ikon.projectmanagement.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +@Entity +@Table(name = "employees") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Employee { + + @Id + @Column(unique = true) + private String empId; + + @JdbcTypeCode(SqlTypes.UUID) + private UUID accountId; + + private String name; + private String email; + private String organizationEmail; + + @Column(name = "\"role\"") + private String role; + + private String grade; + private Boolean active; + + private String highestQualification; + private String joiningDate; + private String confirmationDate; +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/FxRateDetailsEntity.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/FxRateDetailsEntity.java new file mode 100644 index 0000000..8206c7c --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/FxRateDetailsEntity.java @@ -0,0 +1,41 @@ +package com.ikon.projectmanagement.entity; + +import java.util.UUID; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "fx_rate_details_v2") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FxRateDetailsEntity { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + @Column(name = "id") + private UUID id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "fx_rate_id", nullable = false) + @ToString.Exclude + @EqualsAndHashCode.Exclude + private FxRateEntity fxRateEntity; + + @Column(name = "currency_id", nullable = false) + private String currencyId; + + @Column(name = "currency") + private String currency; + + @Column(name = "fx_rate") + private Double fxRate; + + @Column(name = "active_status") + private Boolean activeStatus; + + @Column(name = "year") + private String year; +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/FxRateEntity.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/FxRateEntity.java new file mode 100644 index 0000000..9ef4575 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/FxRateEntity.java @@ -0,0 +1,36 @@ +package com.ikon.projectmanagement.entity; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "fx_rates_v2") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FxRateEntity { + + @Id + @Column(name = "fx_rate_id") + private UUID fxRateId; + + @Column(name = "account_identifier") + private UUID accountIdentifier; + + @OneToMany(mappedBy = "fxRateEntity", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + @MapKey(name = "currencyId") + @Builder.Default + @ToString.Exclude + @EqualsAndHashCode.Exclude + private Map fxRateDetails = new HashMap<>(); + + public void addFxRateDetail(FxRateDetailsEntity detail) { + detail.setFxRateEntity(this); + this.fxRateDetails.put(detail.getCurrencyId(), detail); + } +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Grade.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Grade.java new file mode 100644 index 0000000..ff023c0 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Grade.java @@ -0,0 +1,32 @@ +package com.ikon.projectmanagement.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +@Entity +@Table(name = "grade") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Grade { + + @Id + @JdbcTypeCode(SqlTypes.UUID) + private UUID id; + + @JdbcTypeCode(SqlTypes.UUID) + private UUID accountId; + + @Column(nullable = false) + private String grade; +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Issue.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Issue.java new file mode 100644 index 0000000..e226f12 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Issue.java @@ -0,0 +1,93 @@ +package com.ikon.projectmanagement.entity; + +import jakarta.persistence.*; +import lombok.*; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +@Entity +@Table(name = "issues") +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Issue { + + @Id + @GeneratedValue + @JdbcTypeCode(SqlTypes.UUID) + @Column(name = "issue_id", nullable = false) + private UUID issueId; + + @Column(name = "issue_title", nullable = false) + private String issueTitle; + + @Column(name = "issue_probability") + private Integer issueProbability; + + @Column(name = "gross_issue_value") + private BigDecimal grossIssueValue; + + @Column(name = "probable_issue_value") + private BigDecimal probableIssueValue; + + @Column(name = "probable_issue_value_usd") + private BigDecimal probableIssueValueInUSD; + + @Column(name = "issue_impact") + private String issueImpact; + + @Column(name = "issue_owner") + private String issueOwner; + + @Column(name = "issue_description", columnDefinition = "TEXT") + private String issueDescription; + + @Column(name = "mitigation_action", columnDefinition = "TEXT") + private String mitigationAction; + + @Column(name = "project_identifier", nullable = false) + private UUID projectIdentifier; + + @Column(name = "financial_issue") + private Boolean financialIssue; + + @Column(name = "issue_created_date") + private LocalDateTime issueCreatedDate; + + @Column(name = "issue_options_select_id") + private String issueOptionsSelectId; + + @Column(name = "issue_status") + private String issueStatus; + + @Column(name = "issue_age") + private Integer issueAge; + + @Column(name = "effected_sprint_id") + private String effectedSprintId; + + @Column(name = "created_at") + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + @PrePersist + protected void onCreate() { + this.createdAt = LocalDateTime.now(); + if (this.issueCreatedDate == null) { + this.issueCreatedDate = LocalDateTime.now(); + } + } + + @PreUpdate + protected void onUpdate() { + this.updatedAt = LocalDateTime.now(); + } +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Meeting.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Meeting.java new file mode 100644 index 0000000..ab07e47 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Meeting.java @@ -0,0 +1,139 @@ +package com.ikon.projectmanagement.entity; + +import jakarta.persistence.*; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "meetings") +public class Meeting { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + @Column(name = "id", updatable = false, nullable = false) + private UUID id; + + @Column(name = "title") + private String title; + + @Column(name = "project_name") + private String projectName; + + @Column(nullable = true) + private UUID createdBy; + + @Column(nullable = true) + private UUID accountId; + + @Column(name = "place") + private String place; + + @Column(name = "date") + private LocalDate date; + + @Column(name = "time") + private String time; + + @Column(name = "duration") + private String duration; + + @Column(name = "called_by") + private String calledBy; + + @ElementCollection + @CollectionTable(name = "meeting_attendees", joinColumns = @JoinColumn(name = "meeting_id")) + private List attendees; + + @ElementCollection + @CollectionTable(name = "meeting_agenda", joinColumns = @JoinColumn(name = "meeting_id")) + private List agenda; + + @ElementCollection + @CollectionTable(name = "meeting_decisions", joinColumns = @JoinColumn(name = "meeting_id")) + @Column(name = "decision", columnDefinition = "TEXT") + private List decisions; + + @ElementCollection + @CollectionTable(name = "meeting_actions", joinColumns = @JoinColumn(name = "meeting_id")) + private List actions; + + @Column(name = "notes", columnDefinition = "TEXT") + private String notes; + + @Column(name = "project_identifier", nullable = false) +private UUID projectIdentifier; + + @Column(name = "status") + private String status; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @Embeddable + public static class Attendee { + + @Column(name = "name") + private String name; + + @Column(name = "role") + private String role; + + @Column(name = "contact") + private String contact; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @Embeddable + public static class AgendaItem { + + @Column(name = "item", columnDefinition = "TEXT") + private String item; + + @Column(name = "owner") + private String owner; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @Embeddable + public static class ActionItem { + + @Column(name = "item", columnDefinition = "TEXT") + private String item; + + @Column(name = "responsible") + private String responsible; + + @Column(name = "due_date") + private LocalDate dueDate; + } +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProductOfProject.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProductOfProject.java new file mode 100644 index 0000000..598dbc0 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProductOfProject.java @@ -0,0 +1,80 @@ +package com.ikon.projectmanagement.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.util.*; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import com.ikon.projectmanagement.dto.response.ProductExpenseResponseDto; + +@Entity +@Table(name = "product_project") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ProductOfProject { + + @Id + @GeneratedValue + @JdbcTypeCode(SqlTypes.UUID) + @Column(name = "product_identifier") + private UUID productIdentifier; + + @Column(name = "project_identifier", nullable = true) + @JdbcTypeCode(SqlTypes.UUID) + private UUID projectIdentifier; + + @Column(name = "project_name", nullable = true) + private String projectName; + + @Column(name = "project_manager", nullable = true) + @JdbcTypeCode(SqlTypes.UUID) + private UUID projectManager; + + @Column(nullable = true) + private UUID accountId; + + @Column(nullable = true) + private UUID leadIdentifier; + + @Column(nullable = true) + private String productStatus; + + @Column(nullable = true) + private String updatedOn; + + @Column(nullable = true) + private String projectStatus; + + @Column(nullable = true) + private String productType; + + @Column(columnDefinition = "TEXT", nullable = true) + private String productDescription; + + @Column(nullable = true) + private Double discountPercent; + + @Column(nullable = true) + private String createdOn; + + @Column(nullable = true) + private UUID updatedBy; + + @Column(nullable = true) + private UUID createdBy; + + @OneToMany(mappedBy = "productPS", cascade = CascadeType.ALL, orphanRemoval = true) + private List quotation = new ArrayList<>(); + + @OneToMany(mappedBy = "productPS", cascade = CascadeType.ALL, orphanRemoval = true) + private List resourceDataWithAllocation = new ArrayList<>(); + + @OneToMany(mappedBy = "productPS", cascade = CascadeType.ALL, orphanRemoval = true) + @MapKey(name = "id") + private Map expenseDetails = new HashMap<>(); + +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProductOfProjectExpenseDetail.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProductOfProjectExpenseDetail.java new file mode 100644 index 0000000..aa277a0 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProductOfProjectExpenseDetail.java @@ -0,0 +1,33 @@ +package com.ikon.projectmanagement.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.util.UUID; + +@Entity +@Table(name = "product_project_expense_detail") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ProductOfProjectExpenseDetail { + + @Id + @GeneratedValue + private UUID id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "product_prj_id", nullable = false) + private ProductOfProject productPS; + + private String expenseName; + private String location; + private String currency; + + private Double cost; + private Integer quantity; + private Double totalCost; + + @Column(columnDefinition = "TEXT") + private String remarks; +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProductPSQuotation.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProductPSQuotation.java new file mode 100644 index 0000000..7ce375e --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProductPSQuotation.java @@ -0,0 +1,32 @@ +package com.ikon.projectmanagement.entity; + +import java.util.UUID; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "product_ps_quotation") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ProductPSQuotation { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + private String itemKey; // original map key + + private String role; + private Double totalFTE; + private Double scr; + private Double expenses; + private Double otherCosts; + private Double billingAmount; + + @ManyToOne + @JoinColumn(name = "deal_identifier" ) + private ProductOfProject productPS; +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProductPSResourceAllocation.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProductPSResourceAllocation.java new file mode 100644 index 0000000..fbc3f7c --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProductPSResourceAllocation.java @@ -0,0 +1,48 @@ +package com.ikon.projectmanagement.entity; + +import jakarta.persistence.*; +import lombok.*; + +import java.util.Map; +import java.util.UUID; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +@Entity +@Table(name = "product_ps_resource_allocation") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ProductPSResourceAllocation { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + private String resourceType; + private String role; + private Integer gradeId; + private String employeeName; + private String taskName; + private String resourceId; + private Long taskId; + + // Storing FTE allotments per month: {"Aug_2025": 0.9, "Sep_2025": 0.5} + @ElementCollection + @CollectionTable( + name = "product_ps_resource_allocation_months", + joinColumns = @JoinColumn(name = "allocation_id") + ) + @MapKeyColumn(name = "month") + @Column(name = "fte_value") + private Map allocation; + + @JdbcTypeCode(SqlTypes.JSON) + @Column(columnDefinition = "json", nullable = true) + private Map> detailedAllocation; + + @ManyToOne + @JoinColumn(name = "deal_identifier") + private ProductOfProject productPS; +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Project.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Project.java new file mode 100644 index 0000000..555dde8 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Project.java @@ -0,0 +1,180 @@ +package com.ikon.projectmanagement.entity; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; +import java.util.ArrayList; + +@Entity +@Table(name = "projects", uniqueConstraints = @UniqueConstraint(columnNames = "project_identifier")) +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Project { + + @Id + @JdbcTypeCode(SqlTypes.UUID) + @Column(name = "project_identifier") + private UUID projectIdentifier; + + @JdbcTypeCode(SqlTypes.UUID) + @Column(nullable = false) + private UUID projectManager; + + @Column(nullable = false) + private String projectName; + + @Column(nullable = false) + private UUID accountId; + + @Column(nullable = true) + private String projectStatus; + + @Column(nullable = true) + private String projectNumber; + + @Column(nullable = true) + private String parentProjectNo; + + @Column(nullable = true) + private String contractNumber; + + @Column(nullable = true) + private String projectClient; + + @Column(nullable = true) + private String projectCity; + + @Column(nullable = true) + private String projectCountry; + + @Column(nullable = true) + private String currency; + + @Column(nullable = true) + private String projectImage; + + @Column(nullable = true) + private String contractUpload; + + @Column(nullable = true) + private String source; + + @Column(nullable = true) + private String productType; + + @Column(nullable = true) + private String expenses; + + @Column(nullable = true) + private String formattedActualRevenueIncludingVAT_deal; + + @Column(nullable = true) + private Boolean isCompleted; + + @Column(nullable = true) + private Boolean groupNotExist; + + @Column(nullable = true) + private Boolean isDebtRevenue_deal; + + @Column(length = 2000, nullable = true) + private String projectDescription; + + @JdbcTypeCode(SqlTypes.UUID) + @Column(nullable = true) + private UUID createdById; + + @JdbcTypeCode(SqlTypes.UUID) + @Column(nullable = true) + private UUID updatedBy; + + @JdbcTypeCode(SqlTypes.UUID) + @Column(nullable = true) + private UUID projectManagerDelegates; + + @Column(nullable = true) + private LocalDate projectStartDate; + + @Column(nullable = true) + private LocalDate contractedStartDate; + + @Column(nullable = true) + private LocalDate contractedEndDate; + + @Column(nullable = true) + private LocalDate updatedOn; + + @Column(nullable = true) + private LocalDate createdOn; + + @ElementCollection + @CollectionTable(name = "project_team", joinColumns = @JoinColumn(name = "project_id")) + @Column(name = "user_id", nullable = true) + @JdbcTypeCode(SqlTypes.UUID) + @Builder.Default + private List projectTeam = new ArrayList<>(); + + @ElementCollection + @CollectionTable(name = "project_team_pm", joinColumns = @JoinColumn(name = "project_id")) + @Column(nullable = true) + @JdbcTypeCode(SqlTypes.UUID) + private List projectTeamUnderProjectManager = new ArrayList<>(); + + @ElementCollection + @CollectionTable(name = "project_team_pm_delegates", joinColumns = @JoinColumn(name = "project_id")) + @Column(nullable = true) + @JdbcTypeCode(SqlTypes.UUID) + private List projectTeamUnderProjectManagerDelegates = new ArrayList<>(); + + @Column(length = 2000, nullable = true) + private String groupAssigneesEditStr; + + @Column(length = 2000, nullable = true) + private String groupAssigneesViewStr; + + @JdbcTypeCode(SqlTypes.JSON) + @Column(columnDefinition = "json", nullable = true) + private Map participants; + + @JdbcTypeCode(SqlTypes.JSON) + @Column(columnDefinition = "json", nullable = true) + private Map contractedProductIdentifierWiseDataObj; + + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinColumn(name = "product_identifier", nullable = true) + private ProductOfProject product; + + +@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) +@JoinColumn(name = "project_identifier") +@Builder.Default +private List risks = new ArrayList<>(); + + +@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) +@JoinColumn(name = "project_identifier") +@Builder.Default +private List meetings = new ArrayList<>(); +} + diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProjectSchedule.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProjectSchedule.java new file mode 100644 index 0000000..b565a12 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProjectSchedule.java @@ -0,0 +1,51 @@ +package com.ikon.projectmanagement.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Entity +@Table(name = "project_schedule", uniqueConstraints = { + @UniqueConstraint(columnNames = "project_identifier") +}) +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ProjectSchedule { + + @Id + @GeneratedValue + @JdbcTypeCode(SqlTypes.UUID) + @Column(name = "schedule_identifier") + private UUID scheduleIdentifier; + + @Column(name = "project_identifier", nullable = false) + @JdbcTypeCode(SqlTypes.UUID) + private UUID projectIdentifier; + + @Column(name = "product_identifier", nullable = true) + @JdbcTypeCode(SqlTypes.UUID) + private UUID productIdentifier; + + @Column(name = "account_id", nullable = false) + @JdbcTypeCode(SqlTypes.UUID) + private UUID accountId; + + @OneToMany(mappedBy = "schedule", cascade = CascadeType.ALL, orphanRemoval = true) + @Builder.Default + private List tasks = new ArrayList<>(); + + @OneToMany(mappedBy = "schedule", cascade = CascadeType.ALL, orphanRemoval = true) + @Builder.Default + private List dependencies = new ArrayList<>(); + + @OneToMany(mappedBy = "schedule", cascade = CascadeType.ALL, orphanRemoval = true) + @Builder.Default + private List groups = new ArrayList<>(); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProjectScheduleDependency.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProjectScheduleDependency.java new file mode 100644 index 0000000..1fa7341 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProjectScheduleDependency.java @@ -0,0 +1,27 @@ +package com.ikon.projectmanagement.entity; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "project_schedule_dependency") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ProjectScheduleDependency { + + @Id + @Column(name = "id") + private Long id; + + @Column(name = "predecessor_id") + private Long predecessorId; + + @Column(name = "dependency_type") + private Integer dependencyType; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "schedule_identifier", nullable = false) + private ProjectSchedule schedule; +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProjectScheduleGroup.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProjectScheduleGroup.java new file mode 100644 index 0000000..15e902e --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProjectScheduleGroup.java @@ -0,0 +1,47 @@ +package com.ikon.projectmanagement.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Entity +@Table(name = "project_schedule_group") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ProjectScheduleGroup { + + @Id + @GeneratedValue + @JdbcTypeCode(SqlTypes.UUID) + @Column(name = "id") + private UUID id; + + @Column(name = "group_key", nullable = false) + private String groupKey; + + @Column(name = "group_name") + private String groupName; + + @Column(name = "color") + private String color; + + @ElementCollection + @CollectionTable( + name = "project_schedule_group_task", + joinColumns = @JoinColumn(name = "group_id") + ) + @Column(name = "task_id") + @Builder.Default + private List taskIds = new ArrayList<>(); + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "schedule_identifier", nullable = false) + private ProjectSchedule schedule; +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProjectScheduleTask.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProjectScheduleTask.java new file mode 100644 index 0000000..b799c66 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/ProjectScheduleTask.java @@ -0,0 +1,54 @@ +package com.ikon.projectmanagement.entity; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "project_schedule_task") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ProjectScheduleTask { + + @Id + @Column(name = "id") + private Long id; + + @Column(name = "parent_id") + private Long parentId; + + @Column(name = "task_name") + private String taskName; + + @Column(name = "task_duration") + private Double taskDuration; + + @Column(name = "task_predecessor") + private String taskPredecessor; + + @Column(name = "dependency_type") + private Integer dependencyType; + + @Column(name = "task_colour") + private String taskColour; + + @Column(name = "delay_duration") + private Double delayDuration; + + @Column(name = "task_description", columnDefinition = "TEXT") + private String taskDescription; + + @Column(name = "task_start") + private String taskStart; + + @Column(name = "task_end") + private String taskEnd; + + @Column(name = "milestone_task") + private Boolean milestoneTask; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "schedule_identifier", nullable = false) + private ProjectSchedule schedule; +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Risks.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Risks.java new file mode 100644 index 0000000..224a914 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Risks.java @@ -0,0 +1,91 @@ +package com.ikon.projectmanagement.entity; + +import jakarta.persistence.*; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "risks") +public class Risks { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + @Column(name = "risk_identifier", updatable = false, nullable = false) + private UUID riskIdentifier; + + @Column(name = "risk_title", nullable = false) + private String riskTitle; + + @Column(name = "risk_probability") + private Integer riskProbability; + + @Column(name = "gross_risk_value", precision = 15, scale = 2) + private BigDecimal grossRiskValue; + + @Column(name = "probable_risk_value", precision = 15, scale = 2) + private BigDecimal probableRiskValue; + + @Column(name = "probable_risk_value_in_usd", precision = 15, scale = 2) + private BigDecimal probableRiskValueInUSD; + + @Column(name = "risk_impact") + private String riskImpact; + + @Column(name = "risk_owner") + private String riskOwner; + + @Column(name = "risk_description", columnDefinition = "TEXT") + private String riskDescription; + + @Column(name = "financial_risk") + private Boolean financialRisk; + + @Column(name = "risk_created_date") + private LocalDateTime riskCreatedDate; + + @Column(name = "risk_options_select_id") + private String riskOptionsSelectId; + + @Column(name = "risk_status") + private String riskStatus; + + @Column(name = "risk_age") + private Integer riskAge; + + @Column(name = "effected_sprint_id") + private String effectedSprintId; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + // @Column(name = "project_identifier", insertable = false, updatable = false) + // private UUID projectIdentifier; + + @Column(name = "project_identifier") + private UUID projectIdentifier; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); + if (riskCreatedDate == null) { + riskCreatedDate = LocalDateTime.now(); + } + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Role.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Role.java new file mode 100644 index 0000000..914662c --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/Role.java @@ -0,0 +1,30 @@ +package com.ikon.projectmanagement.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +@Entity +@Table(name = "crm_role") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Role { + + @Id + @JdbcTypeCode(SqlTypes.UUID) + private UUID id; + + @JdbcTypeCode(SqlTypes.UUID) + private UUID accountId; + + private String role; +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/WorkingDaysDetailsEntity.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/WorkingDaysDetailsEntity.java new file mode 100644 index 0000000..8c15caa --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/WorkingDaysDetailsEntity.java @@ -0,0 +1,34 @@ +package com.ikon.projectmanagement.entity; + +import java.util.UUID; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "working_days_details") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WorkingDaysDetailsEntity { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "working_id", nullable = false) + @ToString.Exclude + @EqualsAndHashCode.Exclude + private WorkingDaysEntity workingDaysEntity; + + @Column(name = "year") + private String year; + + @Column(name = "month", nullable = false) + private String month; + + @Column(name = "working_days") + private Integer workingDays; +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/WorkingDaysEntity.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/WorkingDaysEntity.java new file mode 100644 index 0000000..4d584d3 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/entity/WorkingDaysEntity.java @@ -0,0 +1,36 @@ +package com.ikon.projectmanagement.entity; + +import jakarta.persistence.*; +import lombok.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@Entity +@Table(name = "working_days") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WorkingDaysEntity { + + @Id + @Column(name = "working_id") + private UUID workingId; + + @Column(name = "account_identifier") + private UUID accountIdentifier; + + @OneToMany(mappedBy = "workingDaysEntity", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + @MapKey(name = "month") + @Builder.Default + @ToString.Exclude + @EqualsAndHashCode.Exclude + private Map workingDaysDetails = new HashMap<>(); + + public void addWorkingDaysDetail(WorkingDaysDetailsEntity detail) { + detail.setWorkingDaysEntity(this); + this.workingDaysDetails.put(detail.getMonth(), detail); + } +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/EmployeeMapper.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/EmployeeMapper.java new file mode 100644 index 0000000..99c0dfb --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/EmployeeMapper.java @@ -0,0 +1,79 @@ +package com.ikon.projectmanagement.mapper; + +import com.ikon.projectmanagement.dto.request.EmployeeRequestDto; +import com.ikon.projectmanagement.dto.response.EmployeeResponseDto; +import com.ikon.projectmanagement.entity.Employee; +import org.springframework.stereotype.Component; + +/** + * Mapper for converting between Employee entity and DTO objects. + * Handles bidirectional mapping for request and response DTOs. + */ +@Component +public class EmployeeMapper { + + /** + * Convert Employee entity to response DTO + */ + public EmployeeResponseDto toResponseDto(Employee employee) { + if (employee == null) { + return null; + } + return EmployeeResponseDto.builder() + .empId(employee.getEmpId()) + .accountId(employee.getAccountId()) + .name(employee.getName()) + .email(employee.getEmail()) + .organizationEmail(employee.getOrganizationEmail()) + .role(employee.getRole()) + .grade(employee.getGrade()) + .active(employee.getActive()) + .build(); + } + + /** + * Convert request DTO to Employee entity + */ + public Employee toEntity(EmployeeRequestDto dto) { + if (dto == null) { + return null; + } + return Employee.builder() + .empId(dto.getEmpId()) + .name(dto.getName()) + .email(dto.getEmail()) + .organizationEmail(dto.getOrganizationEmail()) + .role(dto.getRole()) + .grade(dto.getGrade()) + .active(dto.getActive()) + .build(); + } + + /** + * Update Employee entity from request DTO + */ + public Employee updateEntity(Employee employee, EmployeeRequestDto dto) { + if (employee == null || dto == null) { + return employee; + } + if (dto.getName() != null) { + employee.setName(dto.getName()); + } + if (dto.getEmail() != null) { + employee.setEmail(dto.getEmail()); + } + if (dto.getOrganizationEmail() != null) { + employee.setOrganizationEmail(dto.getOrganizationEmail()); + } + if (dto.getRole() != null) { + employee.setRole(dto.getRole()); + } + if (dto.getGrade() != null) { + employee.setGrade(dto.getGrade()); + } + if (dto.getActive() != null) { + employee.setActive(dto.getActive()); + } + return employee; + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/GradeMapper.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/GradeMapper.java new file mode 100644 index 0000000..ef32f63 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/GradeMapper.java @@ -0,0 +1,60 @@ +package com.ikon.projectmanagement.mapper; + +import java.util.UUID; + +import com.ikon.projectmanagement.dto.request.GradeRequestDto; +import com.ikon.projectmanagement.dto.response.GradeResponseDto; +import com.ikon.projectmanagement.entity.Grade; +import org.springframework.stereotype.Component; + +/** + * Mapper for converting between Grade entity and DTO objects. + * Handles bidirectional mapping for request and response DTOs. + */ +@Component +public class GradeMapper { + + /** + * Convert Grade entity to response DTO + */ + public GradeResponseDto toResponseDto(Grade grade) { + if (grade == null) { + return null; + } + return GradeResponseDto.builder() + .id(grade.getId()) + .accountId(grade.getAccountId()) + .grade(grade.getGrade()) + .build(); + } + + /** + * Convert request DTO to Grade entity + */ + public Grade toEntity(GradeRequestDto dto) { + if (dto == null) { + return null; + } + // Grade.id has no @GeneratedValue, so the PK must be supplied here or the + // insert fails with a null primary key. Honour a client-supplied id, else + // generate one. accountId is carried through from the request. + return Grade.builder() + .id(dto.getId() != null ? dto.getId() : UUID.randomUUID()) + .accountId(dto.getAccountId()) + .grade(dto.getGrade()) + .build(); + } + + /** + * Update Grade entity from request DTO + */ + public Grade updateEntity(Grade grade, GradeRequestDto dto) { + if (grade == null || dto == null) { + return grade; + } + if (dto.getGrade() != null) { + grade.setGrade(dto.getGrade()); + } + return grade; + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/IssueMapper.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/IssueMapper.java new file mode 100644 index 0000000..6c5130e --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/IssueMapper.java @@ -0,0 +1,112 @@ +package com.ikon.projectmanagement.mapper; + +import org.springframework.stereotype.Component; + +import com.ikon.projectmanagement.dto.request.IssueCreateRequestDto; +import com.ikon.projectmanagement.dto.response.IssueResponseDto; +import com.ikon.projectmanagement.entity.Issue; + +@Component +public class IssueMapper { + + // ================= CREATE ================= + public Issue toEntity(IssueCreateRequestDto dto) { + Issue issue = new Issue(); + + issue.setIssueTitle(dto.getIssueTitle()); + issue.setIssueProbability(dto.getIssueProbability()); + issue.setGrossIssueValue(dto.getGrossIssueValue()); + issue.setProbableIssueValue(dto.getProbableIssueValue()); + issue.setProbableIssueValueInUSD(dto.getProbableIssueValueInUSD()); + issue.setIssueImpact(dto.getIssueImpact()); + issue.setIssueOwner(dto.getIssueOwner()); + issue.setIssueDescription(dto.getIssueDescription()); + issue.setMitigationAction(dto.getMitigationAction()); + issue.setFinancialIssue(dto.getFinancialIssue()); + issue.setIssueCreatedDate(dto.getIssueCreatedDate()); + issue.setIssueOptionsSelectId(dto.getIssueOptionsSelectId()); + issue.setIssueStatus(dto.getIssueStatus()); + issue.setIssueAge(dto.getIssueAge()); + issue.setEffectedSprintId(dto.getEffectedSprintId()); + issue.setProjectIdentifier(dto.getProjectIdentifier()); + + return issue; + } + + // ================= RESPONSE ================= + public IssueResponseDto toResponse(Issue issue) { + IssueResponseDto dto = new IssueResponseDto(); + + dto.setIssueId(issue.getIssueId()); + dto.setIssueTitle(issue.getIssueTitle()); + dto.setIssueProbability(issue.getIssueProbability()); + dto.setGrossIssueValue(issue.getGrossIssueValue()); + dto.setProbableIssueValue(issue.getProbableIssueValue()); + dto.setProbableIssueValueInUSD(issue.getProbableIssueValueInUSD()); + dto.setIssueImpact(issue.getIssueImpact()); + dto.setIssueOwner(issue.getIssueOwner()); + dto.setIssueDescription(issue.getIssueDescription()); + dto.setMitigationAction(issue.getMitigationAction()); + dto.setFinancialIssue(issue.getFinancialIssue()); + dto.setIssueCreatedDate(issue.getIssueCreatedDate()); + dto.setIssueOptionsSelectId(issue.getIssueOptionsSelectId()); + dto.setIssueStatus(issue.getIssueStatus()); + dto.setIssueAge(issue.getIssueAge()); + dto.setEffectedSprintId(issue.getEffectedSprintId()); + dto.setProjectIdentifier(issue.getProjectIdentifier()); + + return dto; + } + + // ================= UPDATE ================= + public void updateEntityFromDto(IssueCreateRequestDto dto, Issue issue) { + + if (dto.getIssueTitle() != null) + issue.setIssueTitle(dto.getIssueTitle()); + + if (dto.getIssueProbability() != null) + issue.setIssueProbability(dto.getIssueProbability()); + + if (dto.getGrossIssueValue() != null) + issue.setGrossIssueValue(dto.getGrossIssueValue()); + + if (dto.getProbableIssueValue() != null) + issue.setProbableIssueValue(dto.getProbableIssueValue()); + + if (dto.getProbableIssueValueInUSD() != null) + issue.setProbableIssueValueInUSD(dto.getProbableIssueValueInUSD()); + + if (dto.getIssueImpact() != null) + issue.setIssueImpact(dto.getIssueImpact()); + + if (dto.getIssueOwner() != null) + issue.setIssueOwner(dto.getIssueOwner()); + + if (dto.getIssueDescription() != null) + issue.setIssueDescription(dto.getIssueDescription()); + + if (dto.getMitigationAction() != null) + issue.setMitigationAction(dto.getMitigationAction()); + + if (dto.getFinancialIssue() != null) + issue.setFinancialIssue(dto.getFinancialIssue()); + + if (dto.getIssueCreatedDate() != null) + issue.setIssueCreatedDate(dto.getIssueCreatedDate()); + + if (dto.getIssueOptionsSelectId() != null) + issue.setIssueOptionsSelectId(dto.getIssueOptionsSelectId()); + + if (dto.getIssueStatus() != null) + issue.setIssueStatus(dto.getIssueStatus()); + + if (dto.getIssueAge() != null) + issue.setIssueAge(dto.getIssueAge()); + + if (dto.getEffectedSprintId() != null) + issue.setEffectedSprintId(dto.getEffectedSprintId()); + + if (dto.getProjectIdentifier() != null) + issue.setProjectIdentifier(dto.getProjectIdentifier()); + } +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/MeetingMapper.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/MeetingMapper.java new file mode 100644 index 0000000..af1b0ff --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/MeetingMapper.java @@ -0,0 +1,263 @@ +package com.ikon.projectmanagement.mapper; + +import com.ikon.projectmanagement.dto.request.*; +import com.ikon.projectmanagement.dto.response.MeetingResponseDTO; +import com.ikon.projectmanagement.entity.Meeting; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class MeetingMapper { + + // ================= DTO TO ENTITY ================= + + public Meeting toEntity(MeetingRequestDTO dto) { + Meeting meeting = new Meeting(); + + if (dto.getMeeting() != null) { + meeting.setTitle(dto.getMeeting().getTitle()); + meeting.setProjectName(dto.getMeeting().getProjectName()); + meeting.setPlace(dto.getMeeting().getPlace()); + meeting.setDate(parseDate(dto.getMeeting().getDate())); + meeting.setTime(dto.getMeeting().getTime()); + meeting.setDuration(dto.getMeeting().getDuration()); + meeting.setCalledBy(dto.getMeeting().getCalledBy()); + } + + if (dto.getAttendees() != null && dto.getAttendees().getAttendees() != null) { + meeting.setAttendees( + dto.getAttendees().getAttendees().stream() + .map(this::toAttendeeEntity) + .collect(Collectors.toList()) + ); + } + + if (dto.getAgenda() != null && dto.getAgenda().getAgenda() != null) { + meeting.setAgenda( + dto.getAgenda().getAgenda().stream() + .map(this::toAgendaItemEntity) + .collect(Collectors.toList()) + ); + } + + if (dto.getDecisions() != null && dto.getDecisions().getDecisions() != null) { + meeting.setDecisions(dto.getDecisions().getDecisions()); + } + + if (dto.getActions() != null && dto.getActions().getActions() != null) { + meeting.setActions( + dto.getActions().getActions().stream() + .map(this::toActionItemEntity) + .collect(Collectors.toList()) + ); + } + + if (dto.getOthers() != null) { + meeting.setNotes(dto.getOthers().getNotes()); + } + + if (dto.getProjectIdentifier() != null) { + meeting.setProjectIdentifier(dto.getProjectIdentifier()); + } + + meeting.setStatus(dto.getStatus()); + + return meeting; + } + + // ================= UPDATE ENTITY FROM DTO ================= + + public void updateEntityFromDTO(Meeting existing, MeetingRequestDTO dto) { + + if (dto.getMeeting() != null) { + existing.setTitle(dto.getMeeting().getTitle()); + existing.setProjectName(dto.getMeeting().getProjectName()); + existing.setPlace(dto.getMeeting().getPlace()); + existing.setDate(parseDate(dto.getMeeting().getDate())); + existing.setTime(dto.getMeeting().getTime()); + existing.setDuration(dto.getMeeting().getDuration()); + existing.setCalledBy(dto.getMeeting().getCalledBy()); + } + + if (dto.getAttendees() != null && dto.getAttendees().getAttendees() != null) { + existing.setAttendees( + dto.getAttendees().getAttendees().stream() + .map(this::toAttendeeEntity) + .collect(Collectors.toList()) + ); + } + + if (dto.getAgenda() != null && dto.getAgenda().getAgenda() != null) { + existing.setAgenda( + dto.getAgenda().getAgenda().stream() + .map(this::toAgendaItemEntity) + .collect(Collectors.toList()) + ); + } + + if (dto.getDecisions() != null && dto.getDecisions().getDecisions() != null) { + existing.setDecisions(dto.getDecisions().getDecisions()); + } + + if (dto.getActions() != null && dto.getActions().getActions() != null) { + existing.setActions( + dto.getActions().getActions().stream() + .map(this::toActionItemEntity) + .collect(Collectors.toList()) + ); + } + + if (dto.getOthers() != null) { + existing.setNotes(dto.getOthers().getNotes()); + } + + if (dto.getProjectIdentifier() != null) { + existing.setProjectIdentifier(dto.getProjectIdentifier()); + } + + existing.setStatus(dto.getStatus()); + } + + // ================= ENTITY TO DTO ================= + + public MeetingResponseDTO toResponseDTO(Meeting meeting) { + MeetingResponseDTO dto = new MeetingResponseDTO(); + + dto.setId(meeting.getId()); + + MeetingDetailsDTO detailsDTO = new MeetingDetailsDTO(); + detailsDTO.setTitle(meeting.getTitle()); + detailsDTO.setProjectName(meeting.getProjectName()); + detailsDTO.setPlace(meeting.getPlace()); + detailsDTO.setDate(formatDate(meeting.getDate())); + detailsDTO.setTime(meeting.getTime()); + detailsDTO.setDuration(meeting.getDuration()); + detailsDTO.setCalledBy(meeting.getCalledBy()); + dto.setMeeting(detailsDTO); + + AttendeesWrapperDTO attendeesWrapper = new AttendeesWrapperDTO(); + attendeesWrapper.setAttendees( + meeting.getAttendees() != null + ? meeting.getAttendees().stream() + .map(this::toAttendeeDTO) + .collect(Collectors.toList()) + : Collections.emptyList() + ); + dto.setAttendees(attendeesWrapper); + + AgendaWrapperDTO agendaWrapper = new AgendaWrapperDTO(); + agendaWrapper.setAgenda( + meeting.getAgenda() != null + ? meeting.getAgenda().stream() + .map(this::toAgendaItemDTO) + .collect(Collectors.toList()) + : Collections.emptyList() + ); + dto.setAgenda(agendaWrapper); + + DecisionsWrapperDTO decisionsWrapper = new DecisionsWrapperDTO(); + decisionsWrapper.setDecisions( + meeting.getDecisions() != null + ? meeting.getDecisions() + : Collections.emptyList() + ); + dto.setDecisions(decisionsWrapper); + + ActionsWrapperDTO actionsWrapper = new ActionsWrapperDTO(); + actionsWrapper.setActions( + meeting.getActions() != null + ? meeting.getActions().stream() + .map(this::toActionItemDTO) + .collect(Collectors.toList()) + : Collections.emptyList() + ); + dto.setActions(actionsWrapper); + + OthersDTO othersDTO = new OthersDTO(); + othersDTO.setNotes(meeting.getNotes()); + dto.setOthers(othersDTO); + + if (meeting.getProjectIdentifier() != null) { + dto.setProjectIdentifier(meeting.getProjectIdentifier()); + } + + dto.setStatus(meeting.getStatus()); + dto.setCreatedAt(meeting.getCreatedAt()); + dto.setUpdatedAt(meeting.getUpdatedAt()); + + return dto; + } + + // ================= LIST MAPPING ================= + + public List toResponseDTOList(List meetings) { + return meetings.stream() + .map(this::toResponseDTO) + .collect(Collectors.toList()); + } + + // ================= PRIVATE HELPERS ================= + + private LocalDate parseDate(String dateStr) { + if (dateStr == null || dateStr.isBlank()) return null; + try { + return LocalDate.parse(dateStr); + } catch (Exception e) { + return null; + } + } + + private String formatDate(LocalDate date) { + return date != null ? date.toString() : null; + } + + private Meeting.Attendee toAttendeeEntity(AttendeeDTO dto) { + Meeting.Attendee attendee = new Meeting.Attendee(); + attendee.setName(dto.getName()); + attendee.setRole(dto.getRole()); + attendee.setContact(dto.getContact()); + return attendee; + } + + private AttendeeDTO toAttendeeDTO(Meeting.Attendee attendee) { + AttendeeDTO dto = new AttendeeDTO(); + dto.setName(attendee.getName()); + dto.setRole(attendee.getRole()); + dto.setContact(attendee.getContact()); + return dto; + } + + private Meeting.AgendaItem toAgendaItemEntity(AgendaItemDTO dto) { + Meeting.AgendaItem agendaItem = new Meeting.AgendaItem(); + agendaItem.setItem(dto.getItem()); + agendaItem.setOwner(dto.getOwner()); + return agendaItem; + } + + private AgendaItemDTO toAgendaItemDTO(Meeting.AgendaItem agendaItem) { + AgendaItemDTO dto = new AgendaItemDTO(); + dto.setItem(agendaItem.getItem()); + dto.setOwner(agendaItem.getOwner()); + return dto; + } + + private Meeting.ActionItem toActionItemEntity(ActionItemDTO dto) { + Meeting.ActionItem actionItem = new Meeting.ActionItem(); + actionItem.setItem(dto.getItem()); + actionItem.setResponsible(dto.getResponsible()); + actionItem.setDueDate(parseDate(dto.getDueDate())); + return actionItem; + } + + private ActionItemDTO toActionItemDTO(Meeting.ActionItem actionItem) { + ActionItemDTO dto = new ActionItemDTO(); + dto.setItem(actionItem.getItem()); + dto.setResponsible(actionItem.getResponsible()); + dto.setDueDate(formatDate(actionItem.getDueDate())); + return dto; + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/ProductOfProjectMapper.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/ProductOfProjectMapper.java new file mode 100644 index 0000000..9750c5a --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/ProductOfProjectMapper.java @@ -0,0 +1,198 @@ +package com.ikon.projectmanagement.mapper; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; + +import org.springframework.stereotype.Component; + +import com.ikon.projectmanagement.dto.request.ProductOfProjectRequestDto; +import com.ikon.projectmanagement.dto.response.ProductExpenseResponseDto; +import com.ikon.projectmanagement.dto.response.ProductOfProjectResponseDto; +import com.ikon.projectmanagement.dto.response.ProductPSResourceAllocationResponseDto; +import com.ikon.projectmanagement.entity.ProductOfProject; +import com.ikon.projectmanagement.entity.ProductOfProjectExpenseDetail; +import com.ikon.projectmanagement.entity.ProductPSResourceAllocation; + +@Component +public class ProductOfProjectMapper { + + public ProductOfProject toEntity(ProductOfProjectRequestDto dto) { + if (dto == null) + return null; + + ProductOfProject product = new ProductOfProject(); + + product.setProjectIdentifier(dto.getProjectIdentifier()); + product.setProjectName(dto.getProjectName()); + product.setProjectManager(dto.getProjectManager()); + product.setAccountId(dto.getAccountId()); + product.setLeadIdentifier(dto.getLeadIdentifier()); + product.setProductStatus(dto.getProductStatus()); + product.setProjectStatus(dto.getProjectStatus()); + product.setProductType(dto.getProductType()); + product.setProductDescription(dto.getProductDescription()); + product.setDiscountPercent(dto.getDiscountPercent()); + + if (dto.getExpenseDetails() != null && !dto.getExpenseDetails().isEmpty()) { + + Map expenseMap = new HashMap<>(); + + for (Entry entry : dto.getExpenseDetails().entrySet()) { + + UUID expenseId = entry.getKey(); + ProductExpenseResponseDto expDto = entry.getValue(); + + ProductOfProjectExpenseDetail exp = toExpenseEntity(expDto); + exp.setId(expenseId); + exp.setProductPS(product); + + expenseMap.put(expenseId, exp); + } + + product.setExpenseDetails(expenseMap); + } + + return product; + } + + public ProductOfProjectResponseDto toResponse(ProductOfProject entity) { + if (entity == null) + return null; + + ProductOfProjectResponseDto dto = new ProductOfProjectResponseDto(); + + dto.setProductIdentifier(entity.getProductIdentifier()); + dto.setProjectIdentifier(entity.getProjectIdentifier()); + dto.setProjectName(entity.getProjectName()); + dto.setProjectManager(entity.getProjectManager()); + dto.setAccountId(entity.getAccountId()); + dto.setLeadIdentifier(entity.getLeadIdentifier()); + dto.setProductStatus(entity.getProductStatus()); + dto.setProjectStatus(entity.getProjectStatus()); + dto.setProductType(entity.getProductType()); + dto.setProductDescription(entity.getProductDescription()); + dto.setDiscountPercent(entity.getDiscountPercent()); + dto.setCreatedOn(entity.getCreatedOn()); + dto.setUpdatedOn(entity.getUpdatedOn()); + dto.setCreatedBy(entity.getCreatedBy()); + dto.setUpdatedBy(entity.getUpdatedBy()); + + if (entity.getExpenseDetails() != null && !entity.getExpenseDetails().isEmpty()) { + + Map expenseDtos = new HashMap<>(); + + for (Map.Entry entry : entity.getExpenseDetails().entrySet()) { + + ProductOfProjectExpenseDetail exp = entry.getValue(); + + ProductExpenseResponseDto expDto = new ProductExpenseResponseDto(); + expDto.setExpenseName(exp.getExpenseName()); + expDto.setLocation(exp.getLocation()); + expDto.setCurrency(exp.getCurrency()); + expDto.setCost(exp.getCost()); + expDto.setQuantity(exp.getQuantity()); + expDto.setTotalCost(exp.getTotalCost()); + expDto.setRemarks(exp.getRemarks()); + + expenseDtos.put(entry.getKey(), expDto); + } + + dto.setExpenseDetails(expenseDtos); + } + + return dto; + } + + public void updateEntity(ProductOfProject entity, ProductOfProjectRequestDto dto) { + if (dto == null || entity == null) + return; + + if (dto.getProjectIdentifier() != null) + entity.setProjectIdentifier(dto.getProjectIdentifier()); + + if (dto.getProjectName() != null) + entity.setProjectName(dto.getProjectName()); + + if (dto.getProjectManager() != null) + entity.setProjectManager(dto.getProjectManager()); + + if (dto.getLeadIdentifier() != null) + entity.setLeadIdentifier(dto.getLeadIdentifier()); + + if (dto.getProductStatus() != null) + entity.setProductStatus(dto.getProductStatus()); + + if (dto.getProjectStatus() != null) + entity.setProjectStatus(dto.getProjectStatus()); + + if (dto.getProductType() != null) + entity.setProductType(dto.getProductType()); + + if (dto.getProductDescription() != null) + entity.setProductDescription(dto.getProductDescription()); + + if (dto.getDiscountPercent() != null) + entity.setDiscountPercent(dto.getDiscountPercent()); + + } + + public ProductOfProjectExpenseDetail toExpenseEntity(ProductExpenseResponseDto expDto) { + if (expDto == null) + return null; + + return ProductOfProjectExpenseDetail.builder() + .expenseName(expDto.getExpenseName()) + .location(expDto.getLocation()) + .currency(expDto.getCurrency()) + .cost(expDto.getCost()) + .quantity(expDto.getQuantity()) + .totalCost(expDto.getTotalCost()) + .remarks(expDto.getRemarks()) + .build(); + } + + public void updateExpenseEntity(ProductOfProjectExpenseDetail entity, + ProductExpenseResponseDto expenseDto) { + + if (entity == null || expenseDto == null) + return; + + if (expenseDto.getExpenseName() != null) + entity.setExpenseName(expenseDto.getExpenseName()); + + if (expenseDto.getLocation() != null) + entity.setLocation(expenseDto.getLocation()); + + if (expenseDto.getCurrency() != null) + entity.setCurrency(expenseDto.getCurrency()); + + if (expenseDto.getCost() != null) + entity.setCost(expenseDto.getCost()); + + if (expenseDto.getQuantity() != null) + entity.setQuantity(expenseDto.getQuantity()); + + if (expenseDto.getTotalCost() != null) + entity.setTotalCost(expenseDto.getTotalCost()); + + if (expenseDto.getRemarks() != null) + entity.setRemarks(expenseDto.getRemarks()); + } + + private ProductPSResourceAllocationResponseDto toResourceAllocationResponseDto(ProductPSResourceAllocation allocation) { + ProductPSResourceAllocationResponseDto dto = new ProductPSResourceAllocationResponseDto(); + dto.setId(allocation.getId()); + dto.setResourceType(allocation.getResourceType()); + dto.setRole(allocation.getRole()); + dto.setGradeId(allocation.getGradeId()); + dto.setEmployeeName(allocation.getEmployeeName()); + dto.setTaskName(allocation.getTaskName()); + dto.setResourceId(allocation.getResourceId()); + dto.setTaskId(allocation.getTaskId()); + dto.setAllocation(allocation.getAllocation()); + dto.setDetailedAllocation(allocation.getDetailedAllocation()); + return dto; + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/ProductResourceAllocationMapper.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/ProductResourceAllocationMapper.java new file mode 100644 index 0000000..f08371e --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/ProductResourceAllocationMapper.java @@ -0,0 +1,74 @@ +package com.ikon.projectmanagement.mapper; + +import java.util.HashMap; + +import org.springframework.stereotype.Component; + +import com.ikon.projectmanagement.dto.request.ProductPSResourceAllocationRequestDto; +import com.ikon.projectmanagement.dto.response.ProductPSResourceAllocationResponseDto; +import com.ikon.projectmanagement.entity.ProductOfProject; +import com.ikon.projectmanagement.entity.ProductPSResourceAllocation; + +@Component +public class ProductResourceAllocationMapper { + + public ProductPSResourceAllocationResponseDto toResponse(ProductPSResourceAllocation entity) { + if (entity == null) + return null; + + ProductPSResourceAllocationResponseDto dto = new ProductPSResourceAllocationResponseDto(); + dto.setId(entity.getId()); + dto.setResourceType(entity.getResourceType()); + dto.setRole(entity.getRole()); + dto.setGradeId(entity.getGradeId()); + dto.setEmployeeName(entity.getEmployeeName()); + dto.setTaskName(entity.getTaskName()); + dto.setResourceId(entity.getResourceId()); + dto.setTaskId(entity.getTaskId()); + dto.setAllocation(entity.getAllocation()); + dto.setDetailedAllocation(entity.getDetailedAllocation()); + return dto; + } + + public ProductPSResourceAllocation toEntity(ProductPSResourceAllocationRequestDto dto, ProductOfProject product) { + if (dto == null) + return null; + + ProductPSResourceAllocation allocation = new ProductPSResourceAllocation(); + allocation.setResourceType(dto.getResourceType()); + allocation.setRole(dto.getRole()); + allocation.setGradeId(dto.getGradeId()); + allocation.setEmployeeName(dto.getEmployeeName()); + allocation.setTaskName(dto.getTaskName()); + allocation.setResourceId(dto.getResourceId()); + allocation.setTaskId(dto.getTaskId()); + allocation.setAllocation(dto.getAllocation() != null ? dto.getAllocation() : new HashMap<>()); + allocation.setDetailedAllocation(dto.getDetailedAllocation() != null ? dto.getDetailedAllocation() : new HashMap<>()); + allocation.setProductPS(product); + return allocation; + } + + public void updateEntity(ProductPSResourceAllocation entity, ProductPSResourceAllocationRequestDto dto) { + if (entity == null || dto == null) + return; + + if (dto.getResourceType() != null) + entity.setResourceType(dto.getResourceType()); + if (dto.getRole() != null) + entity.setRole(dto.getRole()); + if (dto.getGradeId() != null) + entity.setGradeId(dto.getGradeId()); + if (dto.getEmployeeName() != null) + entity.setEmployeeName(dto.getEmployeeName()); + if (dto.getTaskName() != null) + entity.setTaskName(dto.getTaskName()); + if (dto.getResourceId() != null) + entity.setResourceId(dto.getResourceId()); + if (dto.getTaskId() != null) + entity.setTaskId(dto.getTaskId()); + if (dto.getAllocation() != null) + entity.setAllocation(dto.getAllocation()); + if (dto.getDetailedAllocation() != null) + entity.setDetailedAllocation(dto.getDetailedAllocation()); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/ProjectMapper.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/ProjectMapper.java new file mode 100644 index 0000000..f0e2b73 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/ProjectMapper.java @@ -0,0 +1,118 @@ +package com.ikon.projectmanagement.mapper; + +import org.springframework.stereotype.Component; + +import com.ikon.projectmanagement.dto.request.ProductExpenseRequestDto; +import com.ikon.projectmanagement.dto.request.ProjectRequestDto; +import com.ikon.projectmanagement.dto.response.ProjectResponseDto; +import com.ikon.projectmanagement.entity.ProductOfProjectExpenseDetail; +import com.ikon.projectmanagement.entity.Project; + +@Component +public class ProjectMapper { + + public Project toEntity(ProjectRequestDto dto) { + Project project = new Project(); + mapCommonFields(dto, project); + return project; + } + + public void updateEntityFromDto(ProjectRequestDto dto, Project project) { + mapCommonFields(dto, project); + } + + private void mapCommonFields(ProjectRequestDto dto, Project project) { + + project.setProjectName(dto.getProjectName()); + project.setProjectManager(dto.getProjectManager()); + project.setProjectStatus(dto.getProjectStatus()); + project.setProjectNumber(dto.getProjectNumber()); + project.setParentProjectNo(dto.getParentProjectNo()); + project.setContractNumber(dto.getContractNumber()); + project.setProjectClient(dto.getProjectClient()); + project.setProjectCity(dto.getProjectCity()); + project.setProjectCountry(dto.getProjectCountry()); + project.setCurrency(dto.getCurrency()); + project.setProjectImage(dto.getProjectImage()); + project.setContractUpload(dto.getContractUpload()); + project.setSource(dto.getSource()); + project.setProductType(dto.getProductType()); + project.setExpenses(dto.getExpenses()); + project.setFormattedActualRevenueIncludingVAT_deal(dto.getFormattedActualRevenueIncludingVAT_deal()); + project.setIsCompleted(dto.getIsCompleted()); + project.setGroupNotExist(dto.getGroupNotExist()); + project.setIsDebtRevenue_deal(dto.getIsDebtRevenue_deal()); + project.setProjectDescription(dto.getProjectDescription()); + + project.setProjectStartDate(dto.getProjectStartDate()); + project.setContractedStartDate(dto.getContractedStartDate()); + project.setContractedEndDate(dto.getContractedEndDate()); + + project.setProjectTeam(dto.getProjectTeam()); + project.setProjectTeamUnderProjectManager(dto.getProjectTeamUnderProjectManager()); + project.setProjectTeamUnderProjectManagerDelegates(dto.getProjectTeamUnderProjectManagerDelegates()); + + project.setParticipants(dto.getParticipants()); + project.setContractedProductIdentifierWiseDataObj(dto.getContractedProductIdentifierWiseDataObj()); + + } + + public ProjectResponseDto toResponse(Project project) { + + if (project == null) + return null; + + ProjectResponseDto dto = new ProjectResponseDto(); + + dto.setProjectIdentifier(project.getProjectIdentifier()); + dto.setProjectName(project.getProjectName()); + dto.setProjectManager(project.getProjectManager()); + dto.setProjectStatus(project.getProjectStatus()); + dto.setProjectNumber(project.getProjectNumber()); + dto.setParentProjectNo(project.getParentProjectNo()); + dto.setContractNumber(project.getContractNumber()); + + dto.setProjectClient(project.getProjectClient()); + dto.setProjectCity(project.getProjectCity()); + dto.setProjectCountry(project.getProjectCountry()); + dto.setCurrency(project.getCurrency()); + + dto.setProjectImage(project.getProjectImage()); + dto.setContractUpload(project.getContractUpload()); + + dto.setSource(project.getSource()); + dto.setProductType(project.getProductType()); + dto.setExpenses(project.getExpenses()); + + dto.setFormattedActualRevenueIncludingVAT_deal( + project.getFormattedActualRevenueIncludingVAT_deal()); + dto.setIsCompleted(project.getIsCompleted()); + dto.setGroupNotExist(project.getGroupNotExist()); + dto.setIsDebtRevenue_deal(project.getIsDebtRevenue_deal()); + + dto.setProjectDescription(project.getProjectDescription()); + + dto.setProjectStartDate(project.getProjectStartDate()); + dto.setContractedStartDate(project.getContractedStartDate()); + dto.setContractedEndDate(project.getContractedEndDate()); + + dto.setProjectTeam(project.getProjectTeam()); + dto.setProjectTeamUnderProjectManager( + project.getProjectTeamUnderProjectManager()); + dto.setProjectTeamUnderProjectManagerDelegates( + project.getProjectTeamUnderProjectManagerDelegates()); + + dto.setParticipants(project.getParticipants()); + dto.setContractedProductIdentifierWiseDataObj( + project.getContractedProductIdentifierWiseDataObj()); + + if (project.getProduct() != null) { + dto.setProductIdentifier(project.getProduct().getProductIdentifier()); + dto.setProductType(project.getProduct().getProductType()); + } + + return dto; + } + + +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/RiskMapper.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/RiskMapper.java new file mode 100644 index 0000000..f4e5114 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/RiskMapper.java @@ -0,0 +1,88 @@ +package com.ikon.projectmanagement.mapper; + +import org.springframework.stereotype.Component; + +import com.ikon.projectmanagement.dto.request.RiskCreateRequestDto; +import com.ikon.projectmanagement.dto.response.RiskResponseDto; +import com.ikon.projectmanagement.entity.Risks; + +@Component +public class RiskMapper { + + public Risks toEntity(RiskCreateRequestDto dto) { + Risks risk = new Risks(); + risk.setRiskTitle(dto.getRiskTitle()); + risk.setRiskProbability(dto.getRiskProbability()); + risk.setGrossRiskValue(dto.getGrossRiskValue()); + risk.setProbableRiskValue(dto.getProbableRiskValue()); + risk.setProbableRiskValueInUSD(dto.getProbableRiskValueInUSD()); + risk.setRiskImpact(dto.getRiskImpact()); + risk.setRiskOwner(dto.getRiskOwner()); + risk.setRiskDescription(dto.getRiskDescription()); + risk.setFinancialRisk(dto.getFinancialRisk()); + risk.setRiskCreatedDate(dto.getRiskCreatedDate()); + risk.setRiskOptionsSelectId(dto.getRiskOptionsSelectId()); + risk.setRiskStatus(dto.getRiskStatus()); + risk.setRiskAge(dto.getRiskAge()); + risk.setEffectedSprintId(dto.getEffectedSprintId()); + risk.setProjectIdentifier(dto.getProjectIdentifier()); + return risk; + } + + public RiskResponseDto toResponse(Risks risk) { + RiskResponseDto dto = new RiskResponseDto(); + dto.setRiskIdentifier(risk.getRiskIdentifier()); + dto.setRiskTitle(risk.getRiskTitle()); + dto.setRiskProbability(risk.getRiskProbability()); + dto.setGrossRiskValue(risk.getGrossRiskValue()); + dto.setProbableRiskValue(risk.getProbableRiskValue()); + dto.setProbableRiskValueInUSD(risk.getProbableRiskValueInUSD()); + dto.setRiskImpact(risk.getRiskImpact()); + dto.setRiskOwner(risk.getRiskOwner()); + dto.setRiskDescription(risk.getRiskDescription()); + dto.setFinancialRisk(risk.getFinancialRisk()); + dto.setRiskCreatedDate(risk.getRiskCreatedDate()); + dto.setRiskOptionsSelectId(risk.getRiskOptionsSelectId()); + dto.setRiskStatus(risk.getRiskStatus()); + dto.setRiskAge(risk.getRiskAge()); + dto.setEffectedSprintId(risk.getEffectedSprintId()); + // Now using plain UUID field instead of + // risk.getProject().getProjectIdentifier() + dto.setProjectIdentifier(risk.getProjectIdentifier()); + return dto; + } + + public void updateEntityFromDto(RiskCreateRequestDto dto, Risks risk) { + if (dto.getRiskTitle() != null) + risk.setRiskTitle(dto.getRiskTitle()); + if (dto.getRiskProbability() != null) + risk.setRiskProbability(dto.getRiskProbability()); + if (dto.getGrossRiskValue() != null) + risk.setGrossRiskValue(dto.getGrossRiskValue()); + if (dto.getProbableRiskValue() != null) + risk.setProbableRiskValue(dto.getProbableRiskValue()); + if (dto.getProbableRiskValueInUSD() != null) + risk.setProbableRiskValueInUSD(dto.getProbableRiskValueInUSD()); + if (dto.getRiskImpact() != null) + risk.setRiskImpact(dto.getRiskImpact()); + if (dto.getRiskOwner() != null) + risk.setRiskOwner(dto.getRiskOwner()); + if (dto.getRiskDescription() != null) + risk.setRiskDescription(dto.getRiskDescription()); + if (dto.getFinancialRisk() != null) + risk.setFinancialRisk(dto.getFinancialRisk()); + if (dto.getRiskCreatedDate() != null) + risk.setRiskCreatedDate(dto.getRiskCreatedDate()); + if (dto.getRiskOptionsSelectId() != null) + risk.setRiskOptionsSelectId(dto.getRiskOptionsSelectId()); + if (dto.getRiskStatus() != null) + risk.setRiskStatus(dto.getRiskStatus()); + if (dto.getRiskAge() != null) + risk.setRiskAge(dto.getRiskAge()); + if (dto.getEffectedSprintId() != null) + risk.setEffectedSprintId(dto.getEffectedSprintId()); + if (dto.getProjectIdentifier() != null) + risk.setProjectIdentifier(dto.getProjectIdentifier()); + } + +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/RoleMapper.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/RoleMapper.java new file mode 100644 index 0000000..b4f98fc --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/RoleMapper.java @@ -0,0 +1,59 @@ +package com.ikon.projectmanagement.mapper; + +import java.util.UUID; + +import com.ikon.projectmanagement.dto.request.RoleRequestDto; +import com.ikon.projectmanagement.dto.response.RoleResponseDto; +import com.ikon.projectmanagement.entity.Role; +import org.springframework.stereotype.Component; + +/** + * Mapper for converting between Role entity and DTO objects. + * Handles bidirectional mapping for request and response DTOs. + */ +@Component +public class RoleMapper { + + /** + * Convert Role entity to response DTO + */ + public RoleResponseDto toResponseDto(Role role) { + if (role == null) { + return null; + } + return RoleResponseDto.builder() + .id(role.getId()) + .accountId(role.getAccountId()) + .role(role.getRole()) + .build(); + } + + /** + * Convert request DTO to Role entity + */ + public Role toEntity(RoleRequestDto dto) { + if (dto == null) { + return null; + } + // Role.id has no @GeneratedValue, so the PK must be supplied here or the + // insert fails with a null primary key. Honour a client-supplied id, else + // generate one. + return Role.builder() + .id(dto.getId() != null ? dto.getId() : UUID.randomUUID()) + .role(dto.getRole()) + .build(); + } + + /** + * Update Role entity from request DTO + */ + public Role updateEntity(Role role, RoleRequestDto dto) { + if (role == null || dto == null) { + return role; + } + if (dto.getRole() != null) { + role.setRole(dto.getRole()); + } + return role; + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/ScheduleMapper.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/ScheduleMapper.java new file mode 100644 index 0000000..025197a --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/mapper/ScheduleMapper.java @@ -0,0 +1,99 @@ +package com.ikon.projectmanagement.mapper; + +import com.ikon.projectmanagement.dto.request.ScheduleDependencyDto; +import com.ikon.projectmanagement.dto.request.ScheduleGroupDto; +import com.ikon.projectmanagement.dto.request.ScheduleTaskDto; +import com.ikon.projectmanagement.dto.response.ScheduleResponseDto; +import com.ikon.projectmanagement.entity.ProjectSchedule; +import com.ikon.projectmanagement.entity.ProjectScheduleDependency; +import com.ikon.projectmanagement.entity.ProjectScheduleGroup; +import com.ikon.projectmanagement.entity.ProjectScheduleTask; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Component +public class ScheduleMapper { + + public ScheduleResponseDto toResponse(ProjectSchedule schedule) { + List tasks = new ArrayList<>(); + for (ProjectScheduleTask task : schedule.getTasks()) { + ScheduleTaskDto dto = new ScheduleTaskDto(); + dto.setId(task.getId()); + dto.setParentId(task.getParentId()); + dto.setTaskName(task.getTaskName()); + dto.setTaskDuration(task.getTaskDuration()); + dto.setTaskPredecessor(task.getTaskPredecessor()); + dto.setDependencyType(task.getDependencyType()); + dto.setTaskColour(task.getTaskColour()); + dto.setDelayDuration(task.getDelayDuration()); + dto.setTaskDescription(task.getTaskDescription()); + dto.setTaskStart(task.getTaskStart()); + dto.setTaskEnd(task.getTaskEnd()); + dto.setMilestoneTask(task.getMilestoneTask()); + tasks.add(dto); + } + + List dependencies = new ArrayList<>(); + for (ProjectScheduleDependency dep : schedule.getDependencies()) { + ScheduleDependencyDto dto = new ScheduleDependencyDto(); + dto.setId(dep.getId()); + dto.setPredecessorId(dep.getPredecessorId()); + dto.setDependencyType(dep.getDependencyType()); + dependencies.add(dto); + } + + Map groups = new LinkedHashMap<>(); + for (ProjectScheduleGroup g : schedule.getGroups()) { + ScheduleGroupDto dto = new ScheduleGroupDto(); + dto.setId(g.getGroupKey()); + dto.setGroupName(g.getGroupName()); + dto.setColor(g.getColor()); + dto.setTaskIds(new ArrayList<>(g.getTaskIds())); + groups.put(g.getGroupKey(), dto); + } + + return new ScheduleResponseDto(schedule.getProjectIdentifier(), schedule.getProductIdentifier(), tasks, dependencies, groups); + } + + public ProjectScheduleTask toTaskEntity(ScheduleTaskDto dto, ProjectSchedule schedule) { + ProjectScheduleTask task = new ProjectScheduleTask(); + task.setId(dto.getId()); + task.setParentId(dto.getParentId()); + task.setTaskName(dto.getTaskName()); + task.setTaskDuration(dto.getTaskDuration()); + task.setTaskPredecessor(dto.getTaskPredecessor()); + task.setDependencyType(dto.getDependencyType()); + task.setTaskColour(dto.getTaskColour()); + task.setDelayDuration(dto.getDelayDuration()); + task.setTaskDescription(dto.getTaskDescription()); + task.setTaskStart(dto.getTaskStart()); + task.setTaskEnd(dto.getTaskEnd()); + task.setMilestoneTask(dto.getMilestoneTask()); + task.setSchedule(schedule); + return task; + } + + public ProjectScheduleDependency toDependencyEntity(ScheduleDependencyDto dto, ProjectSchedule schedule) { + ProjectScheduleDependency dep = new ProjectScheduleDependency(); + dep.setId(dto.getId()); + dep.setPredecessorId(dto.getPredecessorId()); + dep.setDependencyType(dto.getDependencyType()); + dep.setSchedule(schedule); + return dep; + } + + public ProjectScheduleGroup toGroupEntity(String groupKey, ScheduleGroupDto dto, ProjectSchedule schedule) { + ProjectScheduleGroup g = new ProjectScheduleGroup(); + g.setGroupKey(groupKey); + g.setGroupName(dto.getGroupName()); + g.setColor(dto.getColor()); + g.setTaskIds(dto.getTaskIds() != null ? new ArrayList<>(dto.getTaskIds()) : new ArrayList<>()); + g.setSchedule(schedule); + return g; + } + +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/EmployeeRepository.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/EmployeeRepository.java new file mode 100644 index 0000000..099919c --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/EmployeeRepository.java @@ -0,0 +1,12 @@ +package com.ikon.projectmanagement.repository; + +import com.ikon.projectmanagement.entity.Employee; +import java.util.Optional; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface EmployeeRepository extends JpaRepository { + Optional findByEmpId(String empId); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/FxRateRepository.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/FxRateRepository.java new file mode 100644 index 0000000..706ca1f --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/FxRateRepository.java @@ -0,0 +1,26 @@ +package com.ikon.projectmanagement.repository; + +import java.util.Optional; +import java.util.UUID; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import com.ikon.projectmanagement.entity.FxRateEntity; + +@Repository +public interface FxRateRepository extends JpaRepository { + + @Query("SELECT f FROM FxRateEntity f LEFT JOIN FETCH f.fxRateDetails WHERE f.fxRateId = :id") + Optional findByIdWithDetails(@Param("id") UUID id); + + Optional findByAccountIdentifier(UUID accountIdentifier); + + @Query("SELECT f FROM FxRateEntity f JOIN f.fxRateDetails d WHERE d.year = :year") + Optional findByYear(@Param("year") String year); + + @Query("SELECT f FROM FxRateEntity f JOIN f.fxRateDetails d WHERE d.year = :year AND d.currency = :currency") + Optional findByYearAndCurrency(@Param("year") String year, @Param("currency") String currency); +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/GradeRepository.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/GradeRepository.java new file mode 100644 index 0000000..a4d3435 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/GradeRepository.java @@ -0,0 +1,10 @@ +package com.ikon.projectmanagement.repository; + +import com.ikon.projectmanagement.entity.Grade; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface GradeRepository extends JpaRepository { +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/IssueRepository.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/IssueRepository.java new file mode 100644 index 0000000..944727f --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/IssueRepository.java @@ -0,0 +1,16 @@ +package com.ikon.projectmanagement.repository; + +import java.util.List; +import java.util.UUID; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.ikon.projectmanagement.entity.Issue; + +@Repository +public interface IssueRepository extends JpaRepository { + List findByProjectIdentifier(UUID projectIdentifier); + + List findByIssueOwner(String issueOwner); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/MeetingRepository.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/MeetingRepository.java new file mode 100644 index 0000000..8ac7720 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/MeetingRepository.java @@ -0,0 +1,16 @@ +package com.ikon.projectmanagement.repository; + +import com.ikon.projectmanagement.entity.Meeting; + +import org.apache.kafka.common.Uuid; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.UUID; + +@Repository +public interface MeetingRepository extends JpaRepository { + + List findByProjectIdentifier(UUID projectIdentifier); +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/ProductOfProjectRepository.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/ProductOfProjectRepository.java new file mode 100644 index 0000000..68a10be --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/ProductOfProjectRepository.java @@ -0,0 +1,24 @@ +package com.ikon.projectmanagement.repository; + +import com.ikon.projectmanagement.entity.ProductOfProject; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ProductOfProjectRepository extends JpaRepository { + List findByAccountIdAndProjectIdentifier( + UUID accountId, + UUID projectIdentifier); + + Optional findByAccountIdAndProductIdentifier( + UUID accountId, + UUID productIdentifier); + + Optional findByAccountIdAndProjectIdentifierAndProductIdentifier(UUID accountId, UUID projectIdentifier, + UUID productIdentifier); +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/ProjectRepository.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/ProjectRepository.java new file mode 100644 index 0000000..b77ca53 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/ProjectRepository.java @@ -0,0 +1,36 @@ +package com.ikon.projectmanagement.repository; + +import com.ikon.projectmanagement.dto.response.StatusWiseProjectResponseData; +import com.ikon.projectmanagement.entity.Project; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +@Repository +public interface ProjectRepository extends JpaRepository { + + List findByAccountId(UUID accountId); + + Optional getProjectByProjectIdentifier(UUID projectIdentifier); + + boolean existsByProjectIdentifier(UUID projectIdentifier); + + Optional findByProjectIdentifier(UUID projectIdentifier); + + Optional findByAccountIdAndProjectIdentifier(UUID accountId, UUID projectIdentifier); + + @Query(""" + SELECT new com.ikon.projectmanagement.dto.response.StatusWiseProjectResponseData ( + p.projectStatus, + COUNT(p) + )FROM Project p GROUP BY p.projectStatus + """) + List getProjectStatusCounts(); + + List findByProjectStatusIgnoreCase(String projectStatus); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/ProjectScheduleRepository.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/ProjectScheduleRepository.java new file mode 100644 index 0000000..7051d1a --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/ProjectScheduleRepository.java @@ -0,0 +1,18 @@ +package com.ikon.projectmanagement.repository; + +import com.ikon.projectmanagement.entity.ProjectSchedule; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface ProjectScheduleRepository extends JpaRepository { + Optional findByAccountIdAndProjectIdentifier(UUID accountId, UUID projectIdentifier); + + Optional findByAccountIdAndProductIdentifier(UUID accountId, UUID productIdentifier); + + List findAllByAccountId(UUID accountId); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/RiskRepository.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/RiskRepository.java new file mode 100644 index 0000000..1cf7c5c --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/RiskRepository.java @@ -0,0 +1,22 @@ +package com.ikon.projectmanagement.repository; + +import com.ikon.projectmanagement.entity.Risks; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.UUID; + +@Repository +public interface RiskRepository extends JpaRepository { + + List findByProjectIdentifier(UUID projectIdentifier); + + List findByRiskStatus(String riskStatus); + + List findByRiskOwner(String riskOwner); + + boolean existsByRiskTitleAndProjectIdentifier(String riskTitle, UUID projectIdentifier); + + Risks findByRiskIdentifierAndProjectIdentifier(UUID riskIdentifier, UUID projectIdentifier); +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/RoleRepository.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/RoleRepository.java new file mode 100644 index 0000000..917173e --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/RoleRepository.java @@ -0,0 +1,10 @@ +package com.ikon.projectmanagement.repository; + +import com.ikon.projectmanagement.entity.Role; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface RoleRepository extends JpaRepository { +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/WorkingDaysRepository.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/WorkingDaysRepository.java new file mode 100644 index 0000000..99b963d --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/repository/WorkingDaysRepository.java @@ -0,0 +1,28 @@ +package com.ikon.projectmanagement.repository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import com.ikon.projectmanagement.entity.WorkingDaysEntity; + +@Repository +public interface WorkingDaysRepository extends JpaRepository { + + @Query("SELECT w FROM WorkingDaysEntity w LEFT JOIN FETCH w.workingDaysDetails WHERE w.workingId = :id") + Optional findByIdWithDetails(@Param("id") UUID id); + + Optional findByAccountIdentifier(UUID accountIdentifier); + + List findAllByAccountIdentifier(UUID accountIdentifier); + + Optional findByWorkingId(UUID workingId); + + @Query("SELECT w FROM WorkingDaysEntity w JOIN w.workingDaysDetails d WHERE d.year = :year") + Optional findByYear(@Param("year") String year); +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/DashboardService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/DashboardService.java new file mode 100644 index 0000000..0117368 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/DashboardService.java @@ -0,0 +1,11 @@ +package com.ikon.projectmanagement.service; + +import java.util.List; + +import com.ikon.projectmanagement.dto.response.DashboardWidgetsResponseDto; +import com.ikon.projectmanagement.dto.response.StatusWiseProjectResponseData; + +public interface DashboardService { + DashboardWidgetsResponseDto getDashboardWidgetsData(); + List getStatusWiseProjectData(); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/EmployeeService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/EmployeeService.java new file mode 100644 index 0000000..08084d3 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/EmployeeService.java @@ -0,0 +1,33 @@ +package com.ikon.projectmanagement.service; + +import com.ikon.projectmanagement.dto.request.EmployeeRequestDto; +import com.ikon.projectmanagement.dto.response.EmployeeResponseDto; +import java.util.List; + +public interface EmployeeService { + + /** + * Retrieve all employees + */ + List getAllEmployees(); + + /** + * Retrieve employee by ID + */ + EmployeeResponseDto getEmployeeById(String id); + + /** + * Create a new employee + */ + EmployeeResponseDto createEmployee(EmployeeRequestDto dto); + + /** + * Update an existing employee + */ + EmployeeResponseDto updateEmployee(String id, EmployeeRequestDto dto); + + /** + * Delete an employee by ID + */ + void deleteEmployee(String id); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/FxRateService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/FxRateService.java new file mode 100644 index 0000000..898bafc --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/FxRateService.java @@ -0,0 +1,11 @@ +package com.ikon.projectmanagement.service; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import com.ikon.projectmanagement.dto.response.FxRateResponseDto; + +public interface FxRateService { + Page getAllFxRates(Pageable pageable); + + FxRateResponseDto getFxRateByYear(String year); +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/GradeService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/GradeService.java new file mode 100644 index 0000000..54083e4 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/GradeService.java @@ -0,0 +1,34 @@ +package com.ikon.projectmanagement.service; + +import com.ikon.projectmanagement.dto.request.GradeRequestDto; +import com.ikon.projectmanagement.dto.response.GradeResponseDto; +import java.util.List; +import java.util.UUID; + +public interface GradeService { + + /** + * Retrieve all grades + */ + List getAllGrades(); + + /** + * Retrieve grade by ID + */ + GradeResponseDto getGradeById(UUID id); + + /** + * Create a new grade + */ + GradeResponseDto createGrade(GradeRequestDto dto); + + /** + * Update an existing grade + */ + GradeResponseDto updateGrade(UUID id, GradeRequestDto dto); + + /** + * Delete a grade by ID + */ + void deleteGrade(UUID id); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ImportEmployeeService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ImportEmployeeService.java new file mode 100644 index 0000000..ada18d7 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ImportEmployeeService.java @@ -0,0 +1,10 @@ +package com.ikon.projectmanagement.service; + +import java.util.List; + +import com.ikon.projectmanagement.dto.response.EmployeeResponseDto; + +public interface ImportEmployeeService { + + List importEmployees(String accessToken); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ImportFxRateService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ImportFxRateService.java new file mode 100644 index 0000000..ae32833 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ImportFxRateService.java @@ -0,0 +1,10 @@ +package com.ikon.projectmanagement.service; + +import java.util.List; + +import com.ikon.projectmanagement.dto.response.FxRateDto; + +public interface ImportFxRateService { + + List importFxRates(String accessToken); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ImportGradeService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ImportGradeService.java new file mode 100644 index 0000000..44534e0 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ImportGradeService.java @@ -0,0 +1,10 @@ +package com.ikon.projectmanagement.service; + +import java.util.List; + +import com.ikon.projectmanagement.dto.response.GradeResponseDto; + +public interface ImportGradeService { + + List importGrades(String accessToken); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ImportRoleService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ImportRoleService.java new file mode 100644 index 0000000..c9fe2e8 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ImportRoleService.java @@ -0,0 +1,10 @@ +package com.ikon.projectmanagement.service; + +import java.util.List; + +import com.ikon.projectmanagement.dto.response.RoleResponseDto; + +public interface ImportRoleService { + + List importRoles(String accessToken); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ImportWorkingDayService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ImportWorkingDayService.java new file mode 100644 index 0000000..7ee1e6d --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ImportWorkingDayService.java @@ -0,0 +1,10 @@ +package com.ikon.projectmanagement.service; + +import java.util.List; + +import com.ikon.projectmanagement.dto.response.WorkingDayDto; + +public interface ImportWorkingDayService { + + List importWorkingDays(String accessToken); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/IssueService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/IssueService.java new file mode 100644 index 0000000..6c6eb66 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/IssueService.java @@ -0,0 +1,22 @@ +package com.ikon.projectmanagement.service; + +import com.ikon.projectmanagement.dto.request.IssueCreateRequestDto; +import com.ikon.projectmanagement.dto.request.RiskCreateRequestDto; +import com.ikon.projectmanagement.dto.response.IssueResponseDto; + +import java.util.List; +import java.util.UUID; + +public interface IssueService { + + IssueResponseDto createIssue(IssueCreateRequestDto issueCreateRequestDto); + + List getAllIssues(UUID projectIdentifier); + + + IssueResponseDto getIssueById(UUID issueId); + + IssueResponseDto updateIssue(UUID issueId, IssueCreateRequestDto issueCreateRequestDto); + + List getIssuesByProject(String projectIdentifier); +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/MeetingService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/MeetingService.java new file mode 100644 index 0000000..89db062 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/MeetingService.java @@ -0,0 +1,22 @@ +package com.ikon.projectmanagement.service; + +import com.ikon.projectmanagement.dto.request.MeetingRequestDTO; +import com.ikon.projectmanagement.dto.response.MeetingResponseDTO; + +import java.util.List; +import java.util.UUID; + +public interface MeetingService { + + MeetingResponseDTO createMeeting(MeetingRequestDTO meetingRequestDTO); + + List getAllMeetings(UUID projectIdentifier); + + List getMeetingsByProjectIdentifier(String projectIdentifier); + + MeetingResponseDTO getMeetingById(UUID meetingId); + + MeetingResponseDTO updateMeeting(UUID meetingId, MeetingRequestDTO meetingRequestDTO); + + void deleteMeeting(UUID meetingId); +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ProductOfProjectService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ProductOfProjectService.java new file mode 100644 index 0000000..dd0379d --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ProductOfProjectService.java @@ -0,0 +1,27 @@ +package com.ikon.projectmanagement.service; + +import java.util.List; +import java.util.UUID; + +import com.ikon.projectmanagement.dto.request.ProductOfProjectRequestDto; +import com.ikon.projectmanagement.dto.response.ProductOfProjectResponseDto; + +public interface ProductOfProjectService { + + + List getProductsByProject(UUID projectIdentifier); + + ProductOfProjectResponseDto getProduct(UUID projectIdentifier, UUID productIdentifier); + + ProductOfProjectResponseDto updateProduct( + UUID projectIdentifier, + UUID productIdentifier, + ProductOfProjectRequestDto productDto + ); + + ProductOfProjectResponseDto transitionStatus( + UUID projectIdentifier, + UUID productIdentifier, + String targetStatus + ); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ProductResourceAllocationService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ProductResourceAllocationService.java new file mode 100644 index 0000000..b6ed177 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ProductResourceAllocationService.java @@ -0,0 +1,20 @@ +package com.ikon.projectmanagement.service; + +import com.ikon.projectmanagement.dto.request.ProductPSResourceAllocationRequestDto; +import com.ikon.projectmanagement.dto.response.ProductPSResourceAllocationResponseDto; + +import java.util.List; +import java.util.UUID; + +public interface ProductResourceAllocationService { + + List getResourceAllocations(UUID productIdentifier); + + ProductPSResourceAllocationResponseDto getResourceAllocation(UUID productIdentifier, UUID allocationIdentifier); + + ProductPSResourceAllocationResponseDto createResourceAllocation(UUID productIdentifier, ProductPSResourceAllocationRequestDto dto); + + ProductPSResourceAllocationResponseDto updateResourceAllocation(UUID productIdentifier, UUID allocationIdentifier, ProductPSResourceAllocationRequestDto dto); + + void deleteResourceAllocation(UUID productIdentifier, UUID allocationIdentifier); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ProjectService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ProjectService.java new file mode 100644 index 0000000..4052f57 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ProjectService.java @@ -0,0 +1,21 @@ +package com.ikon.projectmanagement.service; + +import java.util.List; +import java.util.UUID; + +import com.ikon.projectmanagement.dto.request.ProjectRequestDto; +import com.ikon.projectmanagement.dto.response.ProjectResponseDto; +import com.ikon.projectmanagement.dto.response.ProjectTimelineResponseDto; + +public interface ProjectService { + + ProjectResponseDto createProject(ProjectRequestDto projectDto); + + List getAllProjects(); + + ProjectResponseDto getProjectByProjectIdentifier(UUID projectIdentifier); + + ProjectResponseDto updateProject(UUID projectIdentifier, ProjectRequestDto projectDto); + + List getAllActiveProjectsTimeline(); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/RiskService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/RiskService.java new file mode 100644 index 0000000..e192f6d --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/RiskService.java @@ -0,0 +1,20 @@ +package com.ikon.projectmanagement.service; + +import com.ikon.projectmanagement.dto.request.RiskCreateRequestDto; +import com.ikon.projectmanagement.dto.response.RiskResponseDto; + +import java.util.List; +import java.util.UUID; + +public interface RiskService { + + RiskResponseDto createRisk(RiskCreateRequestDto riskCreateRequestDto); + + List getAllRisks(); + + RiskResponseDto getRiskById(UUID riskId); + + RiskResponseDto updateRisk(UUID riskId, RiskCreateRequestDto riskCreateRequestDto); + + List getRisksByProject(String projectIdentifier); +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/RoleService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/RoleService.java new file mode 100644 index 0000000..80ee2ff --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/RoleService.java @@ -0,0 +1,34 @@ +package com.ikon.projectmanagement.service; + +import com.ikon.projectmanagement.dto.request.RoleRequestDto; +import com.ikon.projectmanagement.dto.response.RoleResponseDto; +import java.util.List; +import java.util.UUID; + +public interface RoleService { + + /** + * Retrieve all roles + */ + List getAllRoles(); + + /** + * Retrieve role by ID + */ + RoleResponseDto getRoleById(UUID id); + + /** + * Create a new role + */ + RoleResponseDto createRole(RoleRequestDto dto); + + /** + * Update an existing role + */ + RoleResponseDto updateRole(UUID id, RoleRequestDto dto); + + /** + * Delete a role by ID + */ + void deleteRole(UUID id); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ScheduleService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ScheduleService.java new file mode 100644 index 0000000..1d8a529 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/ScheduleService.java @@ -0,0 +1,13 @@ +package com.ikon.projectmanagement.service; + +import com.ikon.projectmanagement.dto.request.ScheduleRequestDto; +import com.ikon.projectmanagement.dto.response.ScheduleResponseDto; + +import java.util.List; +import java.util.UUID; + +public interface ScheduleService { + ScheduleResponseDto saveSchedule(UUID projectIdentifier, ScheduleRequestDto dto, String accessToken); + ScheduleResponseDto getSchedule(UUID projectIdentifier, String accessToken); + List getAllSchedules(); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/WorkingDayService.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/WorkingDayService.java new file mode 100644 index 0000000..7fc0578 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/WorkingDayService.java @@ -0,0 +1,11 @@ +package com.ikon.projectmanagement.service; + +import com.ikon.projectmanagement.dto.response.WorkingDaysResponseDto; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface WorkingDayService { + public Page getAllWorkingDays(Pageable pageable); + + public WorkingDaysResponseDto getWorkingDaysByYear(String year); +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/DealConnector.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/DealConnector.java new file mode 100644 index 0000000..d5196fc --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/DealConnector.java @@ -0,0 +1,366 @@ +package com.ikon.projectmanagement.service.etl; + +import java.time.LocalDate; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.springframework.stereotype.Component; + +import com.ikon.connector.dto.response.FieldsConfigDto; +import com.ikon.connector.spi.ConnectorDataSync; +import com.ikon.projectmanagement.entity.ProductOfProject; +import com.ikon.projectmanagement.entity.Project; +import com.ikon.projectmanagement.repository.ProjectRepository; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component +@RequiredArgsConstructor +public class DealConnector implements ConnectorDataSync { + + private static final Logger log = LoggerFactory.getLogger(DealConnector.class); + + private final ProjectRepository projectRepository; + + @Override + public List getFieldsConfig() { + List fieldsConfig = new ArrayList<>(); + fieldsConfig + .add(FieldsConfigDto.builder().key("dealIdentifier").label("Deal Identifier").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("dealName").label("Deal Name").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("projectName").label("Project Name").type("string").build()); + fieldsConfig.add( + FieldsConfigDto.builder().key("accountIdentifier").label("Account Identifier").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("dealStatus").label("Deal Status").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("currency").label("Currency").type("string").build()); + fieldsConfig + .add(FieldsConfigDto.builder().key("contractNumber").label("Contract Number").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("contractedStartDate").label("Contracted Start Date") + .type("string").build()); + fieldsConfig.add( + FieldsConfigDto.builder().key("contractedEndDate").label("Contracted End Date").type("string").build()); + fieldsConfig.add( + FieldsConfigDto.builder().key("parentProjectNo").label("Parent Project No").type("string").build()); + fieldsConfig + .add(FieldsConfigDto.builder().key("isDebtRevenue").label("Is Debt Revenue").type("boolean").build()); + fieldsConfig + .add(FieldsConfigDto.builder().key("leadIdentifier").label("Lead Identifier").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("formattedActualRevenueIncludingVAT") + .label("Formatted Actual Revenue Including VAT").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("dealTeam").label("Deal Team").type("array").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("updatedBy").label("Updated By").type("string").build()); + fieldsConfig + .add(FieldsConfigDto.builder().key("dealStartDate").label("Deal Start Date").type("string").build()); + fieldsConfig + .add(FieldsConfigDto.builder().key("productDetails").label("Product Details").type("object").build()); + return fieldsConfig; + } + + @Override + public String getModule() { + return "Deal"; + } + + @Override + @Transactional + public void syncBatch(List> payload) { + + log.info("[DealConnector] Received {} records for sync", payload.size()); + + payload.stream() + .filter(data -> { + + String dealId = toStr(data.get("dealIdentifier")); + String status = toStr(data.get("dealStatus")); + + log.info( + "[DealConnector] Evaluating deal [{}], status=[{}]", + dealId, + status); + + return "WON".equalsIgnoreCase(status); + }) + .forEach(data -> { + + log.info("===================================================="); + log.info("[DealConnector] Processing Deal"); + log.info("===================================================="); + + log.info("[DealConnector] Full Payload : {}", data); + + UUID dealIdentifier = parseUUID(data.get("dealIdentifier")); + + UUID accountId = parseUUID( + data.get("accountIdentifier") != null + ? data.get("accountIdentifier") + : data.get("accountId")); + + UUID leadIdentifier = parseUUID(data.get("leadIdentifier")); + UUID updatedByUUID = parseUUID(data.get("updatedBy")); + + log.info( + "[DealConnector] dealIdentifier=[{}], accountId=[{}], leadIdentifier=[{}], updatedBy=[{}]", + dealIdentifier, + accountId, + leadIdentifier, + updatedByUUID); + + Object rawProductDetails = data.get("productDetails"); + + log.info( + "[DealConnector] productDetails type=[{}]", + rawProductDetails != null + ? rawProductDetails.getClass().getName() + : "null"); + + log.info( + "[DealConnector] productDetails value=[{}]", + rawProductDetails); + + ProductOfProject product = buildProduct( + rawProductDetails, + accountId, + dealIdentifier, + leadIdentifier); + + if (product == null) { + + log.warn( + "[DealConnector] Product creation FAILED for deal [{}]", + dealIdentifier); + + log.warn( + "[DealConnector] Raw productDetails = {}", + rawProductDetails); + + return; + } + + log.info( + "[DealConnector] Product created successfully. productIdentifier=[{}], type=[{}]", + product.getProductIdentifier(), + product.getProductType()); + + String name = data.get("projectName") != null + ? (String) data.get("projectName") + : (String) data.get("dealName"); + + UUID projectManager = product.getProjectManager() != null + ? product.getProjectManager() + : accountId; + + List projectTeam = parseUUIDList(data.get("dealTeam")); + + Project project = Project.builder() + .projectIdentifier(dealIdentifier) + .accountId(accountId) + .projectName(name) + .projectStatus((String) data.get("dealStatus")) + .currency((String) data.get("currency")) + .isDebtRevenue_deal((Boolean) data.get("isDebtRevenue")) + .formattedActualRevenueIncludingVAT_deal( + toStr(data.get("formattedActualRevenueIncludingVAT_contracted"))) + .contractNumber(toStr(data.get("contractNumber"))) + .contractedStartDate(parseDate((String) data.get("contractedStartDate"))) + .contractedEndDate(parseDate((String) data.get("contractedEndDate"))) + .projectStartDate(parseDate((String) data.get("dealStartDate"))) + .parentProjectNo((String) data.get("parentProjectNo")) + .projectManager(projectManager) + .updatedBy(updatedByUUID) + .projectTeam(projectTeam) + .source("SalesCRM") + .product(product) + .build(); + + log.info( + "[DealConnector] Saving project for deal [{}] projectName=[{}]", + dealIdentifier, + name); + + projectRepository.save(project); + + log.info( + "[DealConnector] Project saved successfully for deal [{}]", + dealIdentifier); + }); + } + + private ProductOfProject buildProduct( + Object raw, + UUID accountId, + UUID projectIdentifier, + UUID leadIdentifier) { + + log.info( + "[DealConnector] buildProduct() called. rawType=[{}]", + raw != null ? raw.getClass().getName() : "null"); + + if (raw == null) { + + log.warn( + "[DealConnector] productDetails is NULL for deal [{}]", + projectIdentifier); + + return null; + } + + if (!(raw instanceof Map)) { + + log.warn( + "[DealConnector] productDetails is NOT a Map. type=[{}], value=[{}]", + raw.getClass().getName(), + raw); + + return null; + } + + Map productMap = (Map) raw; + + log.info( + "[DealConnector] productDetails contains {} entries", + productMap.size()); + + if (productMap.isEmpty()) { + + log.warn( + "[DealConnector] productDetails map is EMPTY"); + + return null; + } + + productMap.forEach((key, value) -> { + + log.info( + "[DealConnector] Entry key=[{}], valueType=[{}]", + key, + value != null ? value.getClass().getName() : "null"); + + log.info( + "[DealConnector] Entry value=[{}]", + value); + }); + + for (Object value : productMap.values()) { + + if (!(value instanceof Map)) { + + log.warn( + "[DealConnector] Product entry is not Map : {}", + value); + + continue; + } + + Map pd = (Map) value; + + String productType = toStr(pd.get("productType")); + + log.info( + "[DealConnector] Checking productType=[{}]", + productType); + + if (!"Professional Service".equalsIgnoreCase(productType)) { + + log.info( + "[DealConnector] Skipping product because type is [{}]", + productType); + + continue; + } + + log.info( + "[DealConnector] Professional Service product FOUND"); + + UUID pmUuid = tryParseUUID(toStr(pd.get("projectManager"))); + + UUID productId = tryParseUUID(toStr(pd.get("productIdentifier"))); + + log.info( + "[DealConnector] productIdentifier=[{}], projectManager=[{}]", + productId, + pmUuid); + + return ProductOfProject.builder() + .productIdentifier(productId) + .projectIdentifier(projectIdentifier) + .accountId(accountId) + .leadIdentifier(leadIdentifier) + .productType(productType) + .productDescription( + toStr(pd.get("productDescription"))) + .discountPercent( + toDouble(pd.get("discountPercent"))) + .projectManager(pmUuid) + .productStatus("Active") + .build(); + } + + log.warn( + "[DealConnector] No Professional Service product found in productDetails. productMap={}", + productMap); + + return null; + } + + // --------------------------------------------------------------------------- + // Helpers + // --------------------------------------------------------------------------- + private UUID parseUUID(Object value) { + if (value == null) + return null; + try { + return UUID.fromString(value.toString()); + } catch (Exception e) { + return null; + } + } + + private UUID tryParseUUID(String value) { + if (value == null) + return null; + try { + return UUID.fromString(value); + } catch (Exception e) { + return null; + } + } + + private List parseUUIDList(Object value) { + List result = new ArrayList<>(); + if (!(value instanceof List)) + return result; + for (Object item : (List) value) { + UUID uuid = parseUUID(item); + if (uuid != null) + result.add(uuid); + } + return result; + } + + private LocalDate parseDate(String value) { + if (value == null || value.isBlank()) + return null; + try { + return LocalDate.parse(value); + } catch (DateTimeParseException e) { + return null; + } + } + + private String toStr(Object value) { + return value != null ? String.valueOf(value) : null; + } + + private Double toDouble(Object value) { + if (value instanceof Number) + return ((Number) value).doubleValue(); + return null; + } +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/EmployeeConector.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/EmployeeConector.java new file mode 100644 index 0000000..02cb540 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/EmployeeConector.java @@ -0,0 +1,71 @@ +package com.ikon.projectmanagement.service.etl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.springframework.stereotype.Component; + +import com.ikon.connector.dto.response.FieldsConfigDto; +import com.ikon.connector.spi.ConnectorDataSync; +import com.ikon.projectmanagement.entity.Employee; +import com.ikon.projectmanagement.repository.EmployeeRepository; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; + + +@Component +@RequiredArgsConstructor +public class EmployeeConector implements ConnectorDataSync{ + + private final EmployeeRepository employeeRepository; + + @Override + public List getFieldsConfig() { + List fieldsConfig = new ArrayList<>(); + fieldsConfig.add(FieldsConfigDto.builder().key("empId").label("Employee Identifier").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("name").label("Name").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("role").label("Role").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("grade").label("Grade").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("email").label("Email").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("organizationEmail").label("Organization Email").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("active").label("Active").type("boolean").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("highestQualification").label("Highest Qualification").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("joiningDate").label("Joining Date").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("confirmationDate").label("Confirmation Date").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("accountId").label("Account Identifier").type("string").build()); + return fieldsConfig; + } + + @Override + public String getModule() { + return "Employee"; + } + + @Override + @Transactional + public void syncBatch(List> payload) { + payload.stream() + .forEach(data -> { + + + Employee employee = Employee.builder() + .name(data.get("name") != null ? data.get("name").toString() : null) + .accountId(data.get("accountId") != null ? UUID.fromString(data.get("accountId").toString()) : null) + .empId(data.get("empId") != null ? data.get("empId").toString() : null) + .email(data.get("email") != null ? data.get("email").toString() : null) + .organizationEmail(data.get("organizationEmail") != null ? data.get("organizationEmail").toString() : null) + .role(data.get("role") != null ? data.get("role").toString() : null) + .grade(data.get("grade") != null ? data.get("grade").toString() : null) + .active(data.get("active") != null ? Boolean.parseBoolean(data.get("active").toString()) : null) + .highestQualification(data.get("highestQualification") != null ? data.get("highestQualification").toString(): null) + .joiningDate(data.get("joiningDate") != null ? data.get("joiningDate").toString() : null) + .confirmationDate(data.get("confirmationDate") != null ? data.get("confirmationDate").toString() : null) + .build(); + + employeeRepository.save(employee); + }); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/FxRateConnector.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/FxRateConnector.java new file mode 100644 index 0000000..8962eb8 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/FxRateConnector.java @@ -0,0 +1,184 @@ +package com.ikon.projectmanagement.service.etl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ikon.connector.dto.response.FieldsConfigDto; +import com.ikon.connector.spi.ConnectorDataSync; +import com.ikon.projectmanagement.entity.FxRateDetailsEntity; +import com.ikon.projectmanagement.entity.FxRateEntity; +import com.ikon.projectmanagement.repository.FxRateRepository; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Component +@RequiredArgsConstructor +@Slf4j +public class FxRateConnector implements ConnectorDataSync { + + private final FxRateRepository fxRateRepository; + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public List getFieldsConfig() { + List fieldsConfig = new ArrayList<>(); + fieldsConfig.add(FieldsConfigDto.builder().key("fxRateId").label("FX Rate ID").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("accountIdentifier").label("Account Identifier").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("fxRateDetails").label("FX Rate Details").type("object").build()); + return fieldsConfig; + } + + @Override + public String getModule() { + return "FxRate"; + } + + @Override + @Transactional + public void syncBatch(List> payload) { + if (payload == null || payload.isEmpty()) + return; + + List fxRates = payload.stream() + .map(data -> { + UUID fxRateId = parseUUID(data.get("fxRateId")); + UUID accountIdentifier = parseUUID(data.get("accountIdentifier")); + + if (fxRateId == null) { + log.warn("Skipping record with null fxRateId"); + return null; + } + + FxRateEntity fxRate = fxRateRepository.findByIdWithDetails(fxRateId) + .orElseGet(() -> FxRateEntity.builder() + .fxRateId(fxRateId) + .build()); + + fxRate.setAccountIdentifier(accountIdentifier); + + Map detailsMap = buildDetailsMap(data.get("fxRateDetails")); + log.info("Built detailsMap size: {}", detailsMap.size()); + + if (!detailsMap.isEmpty()) { + fxRate.getFxRateDetails().clear(); + detailsMap.values().forEach(fxRate::addFxRateDetail); + log.info("fxRateDetails after repopulation: {}", fxRate.getFxRateDetails().size()); + } + + return fxRate; + }) + .filter(Objects::nonNull) + .toList(); + + log.info("Saving {} FxRate entities", fxRates.size()); + fxRateRepository.saveAll(fxRates); + } + + private Map buildDetailsMap(Object rawDetails) { + Map resultMap = new HashMap<>(); + + // Convert to JsonNode — handles ObjectNode, String, or Map + JsonNode rootNode = toJsonNode(rawDetails); + if (rootNode == null || rootNode.isNull() || !rootNode.isObject()) { + log.warn("fxRateDetails could not be parsed as an object, type: {}", + rawDetails == null ? "NULL" : rawDetails.getClass().getName()); + return resultMap; + } + + // Outer loop: year keys e.g. "2026", "2025" + rootNode.fields().forEachRemaining(yearEntry -> { + JsonNode currencyMapNode = yearEntry.getValue(); + if (!currencyMapNode.isObject()) + return; + + // Inner loop: currency keys e.g. "USD", "CNY" + currencyMapNode.fields().forEachRemaining(currencyEntry -> { + JsonNode fd = currencyEntry.getValue(); + if (!fd.isObject()) + return; + + // currencyId is null in payload, fallback to currency field + String currencyId = !fd.path("currencyId").isNull() && fd.hasNonNull("currencyId") + ? fd.path("currencyId").asText(null) + : fd.path("currency").asText(null); + + if (currencyId == null) { + log.warn("Skipping detail with null currencyId and currency for year: {}", yearEntry.getKey()); + return; + } + + Boolean active = fd.hasNonNull("activeStatus") + ? fd.path("activeStatus").asBoolean(true) + : Boolean.TRUE; + + FxRateDetailsEntity detail = FxRateDetailsEntity.builder() + .currencyId(currencyId) + .currency(fd.path("currency").asText(null)) + .fxRate(fd.hasNonNull("fxRate") ? fd.path("fxRate").asDouble() : null) + .activeStatus(active) + .year(fd.path("year").asText(null)) + .build(); + + resultMap.put(currencyId, detail); + log.debug("Added detail: currencyId={}, fxRate={}, year={}", + currencyId, detail.getFxRate(), detail.getYear()); + }); + }); + + return resultMap; + } + + /** + * Converts ObjectNode, Map, or JSON String → JsonNode + */ + private JsonNode toJsonNode(Object rawDetails) { + if (rawDetails == null) + return null; + + // Already a JsonNode (e.g. ObjectNode from Jackson) + if (rawDetails instanceof JsonNode) { + return (JsonNode) rawDetails; + } + + // Plain JSON String + if (rawDetails instanceof String) { + try { + return objectMapper.readTree((String) rawDetails); + } catch (Exception e) { + log.error("Failed to parse fxRateDetails JSON string: {}", e.getMessage()); + return null; + } + } + + // Plain Map (e.g. LinkedHashMap) + if (rawDetails instanceof Map) { + return objectMapper.valueToTree(rawDetails); + } + + log.warn("Unrecognized fxRateDetails type: {}", rawDetails.getClass().getName()); + return null; + } + + // --- Utility Helpers --- + + private UUID parseUUID(Object value) { + if (value == null) + return null; + try { + return UUID.fromString(value.toString()); + } catch (Exception e) { + log.warn("Failed to parse UUID from value: {}", value); + return null; + } + } +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/GradeConnector.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/GradeConnector.java new file mode 100644 index 0000000..3490ba4 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/GradeConnector.java @@ -0,0 +1,134 @@ +package com.ikon.projectmanagement.service.etl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.springframework.stereotype.Component; + +import com.ikon.connector.dto.response.FieldsConfigDto; +import com.ikon.connector.spi.ConnectorDataSync; +import com.ikon.projectmanagement.entity.Grade; +import com.ikon.projectmanagement.repository.GradeRepository; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; + +/** + * GradeConnector: Maps external grade/salary grade data to local Grade entity + * + * This connector syncs grade information from an external HR system or database. + * It handles field mapping, type conversion, and persistence of grade records. + */ +@Component +@RequiredArgsConstructor +public class GradeConnector implements ConnectorDataSync { + + private final GradeRepository gradeRepository; + + /** + * Define the configuration for fields that will be synced from external source. + * This specifies the external field names, labels, and their data types. + */ + @Override + public List getFieldsConfig() { + List fieldsConfig = new ArrayList<>(); + + fieldsConfig.add(FieldsConfigDto.builder() + .key("id") + .label("Grade ID") + .type("string") + .build()); + + fieldsConfig.add(FieldsConfigDto.builder() + .key("accountId") + .label("Account ID") + .type("string") + .build()); + + fieldsConfig.add(FieldsConfigDto.builder() + .key("grade") + .label("Grade") + .type("string") + .build()); + + return fieldsConfig; + } + + /** + * Returns the module name for this connector. + * Used for identification and logging purposes. + */ + @Override + public String getModule() { + return "Grade"; + } + + /** + * Core method that processes incoming grade data from external source. + * + * Process flow: + * 1. Extract and parse fields from Map + * 2. Convert data types using utility methods + * 3. Build Grade entity + * 4. Save to repository + * + * @param payload List of Map objects containing grade data from external source + */ + @Override + @Transactional + public void syncBatch(List> payload) { + payload.stream() + .forEach(data -> { + try { + UUID gradeId = parseUUID(data.get("id")); + UUID accountId = parseUUID(data.get("accountId")); + String gradeName = toStr(data.get("grade")); + + // Skip if required fields are missing + if (gradeId == null || gradeName == null) { + System.err.println("Skipping grade record: missing required fields (id or grade)"); + return; + } + + // Build Grade entity with mapped fields + Grade grade = Grade.builder() + .id(gradeId) + .accountId(accountId) + .grade(gradeName) + .build(); + + // Save the grade entity to repository + gradeRepository.save(grade); + + } catch (Exception e) { + // Log error but continue processing other records + System.err.println("Error syncing grade: " + e.getMessage()); + } + }); + } + + // ============ Utility Methods for Type Conversion ============ + + /** + * Parse UUID from Object safely. + * Returns null if value is null or cannot be parsed as UUID. + */ + private UUID parseUUID(Object value) { + if (value == null) return null; + try { + return UUID.fromString(value.toString()); + } catch (Exception e) { + return null; + } + } + + /** + * Convert Object to String safely. + * Returns null if value is null, otherwise returns String representation. + */ + private String toStr(Object value) { + return value != null ? String.valueOf(value) : null; + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/RoleConnector.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/RoleConnector.java new file mode 100644 index 0000000..51828af --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/RoleConnector.java @@ -0,0 +1,94 @@ +package com.ikon.projectmanagement.service.etl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.springframework.stereotype.Component; + +import com.ikon.connector.dto.response.FieldsConfigDto; +import com.ikon.connector.spi.ConnectorDataSync; +import com.ikon.projectmanagement.entity.Role; +import com.ikon.projectmanagement.repository.RoleRepository; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; + +/** + * RoleConnector: Maps external role data to local Role entity. + * + * This connector syncs role information from an external system. + * It handles field mapping, type conversion, and persistence of Role records. + */ +@Component +@RequiredArgsConstructor +public class RoleConnector implements ConnectorDataSync { + + private final RoleRepository roleRepository; + + /** + * Define the configuration for fields that will be synced from the external + * source. + * This specifies the external field names, labels, and their data types. + */ + @Override + public List getFieldsConfig() { + List fieldsConfig = new ArrayList<>(); + + fieldsConfig.add(FieldsConfigDto.builder() + .key("id") + .label("Role ID") + .type("string") + .build()); + + fieldsConfig.add(FieldsConfigDto.builder() + .key("accountId") + .label("Account ID") + .type("string") + .build()); + + fieldsConfig.add(FieldsConfigDto.builder() + .key("role") + .label("Role") + .type("string") + .build()); + + return fieldsConfig; + } + + /** + * Returns the module name for this connector. + * Used for identification and logging purposes. + */ + @Override + public String getModule() { + return "Role"; + } + + /** + * Core method that processes incoming role data from an external source. + * + * Process flow: + * 1. Extract and parse fields from Map + * 2. Convert data types using utility methods + * 3. Build Role entity + * 4. Save to repository + * + * @param payload List of Map objects containing role data from external source + */ + @Override + @Transactional + public void syncBatch(List> payload) { + payload.stream() + .forEach(data -> { + Role role = Role.builder() + .id(data.get("id") != null ? UUID.fromString(data.get("id").toString()) : null) + .accountId(data.get("id") != null ? UUID.fromString(data.get("id").toString()) : null) + .role(data.get("role") != null ? data.get("role").toString() : null) + .build(); + roleRepository.save(role); + }); + } + +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/WorkingDaysConnector.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/WorkingDaysConnector.java new file mode 100644 index 0000000..7a7bf44 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/etl/WorkingDaysConnector.java @@ -0,0 +1,188 @@ +package com.ikon.projectmanagement.service.etl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ikon.connector.dto.response.FieldsConfigDto; +import com.ikon.connector.spi.ConnectorDataSync; +import com.ikon.projectmanagement.entity.WorkingDaysDetailsEntity; +import com.ikon.projectmanagement.entity.WorkingDaysEntity; +import com.ikon.projectmanagement.repository.WorkingDaysRepository; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Component +@RequiredArgsConstructor +@Slf4j +public class WorkingDaysConnector implements ConnectorDataSync { + + private final WorkingDaysRepository workingDaysRepository; + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public List getFieldsConfig() { + List fieldsConfig = new ArrayList<>(); + fieldsConfig.add(FieldsConfigDto.builder().key("workingId").label("Working ID").type("string").build()); + fieldsConfig.add( + FieldsConfigDto.builder().key("accountIdentifier").label("Account Identifier").type("string").build()); + fieldsConfig.add(FieldsConfigDto.builder().key("workingDaysDetails").label("Working Days Details") + .type("object").build()); + return fieldsConfig; + } + + @Override + public String getModule() { + return "WorkingDays"; + } + + @Override + @Transactional + public void syncBatch(List> payload) { + if (payload == null || payload.isEmpty()) + return; + + List workingDaysList = payload.stream() + .map(data -> { + UUID workingId = parseUUID(data.get("workingId")); + UUID accountIdentifier = parseUUID(data.get("accountIdentifier")); + + if (workingId == null) { + log.warn("Skipping record with null workingId"); + return null; + } + + // Fetch existing entity with details eagerly, or create new + WorkingDaysEntity workingDays = workingDaysRepository.findByIdWithDetails(workingId) + .orElseGet(() -> WorkingDaysEntity.builder() + .workingId(workingId) + .build()); + + workingDays.setAccountIdentifier(accountIdentifier); + + log.info("RAW workingDaysDetails type: {}", + data.get("workingDaysDetails") == null ? "NULL" + : data.get("workingDaysDetails").getClass().getName()); + log.info("RAW workingDaysDetails value: {}", data.get("workingDaysDetails")); + + Map detailsMap = buildDetailsMap(data.get("workingDaysDetails")); + log.info("Built detailsMap size: {}", detailsMap.size()); + + if (!detailsMap.isEmpty()) { + // Clear existing managed collection (orphanRemoval deletes old rows) + workingDays.getWorkingDaysDetails().clear(); + // Repopulate via addWorkingDaysDetail to keep parent link intact + detailsMap.values().forEach(workingDays::addWorkingDaysDetail); + log.info("workingDaysDetails after repopulation: {}", + workingDays.getWorkingDaysDetails().size()); + } + + return workingDays; + }) + .filter(Objects::nonNull) + .toList(); + + log.info("Saving {} WorkingDays entities", workingDaysList.size()); + workingDaysRepository.saveAll(workingDaysList); + } + + private Map buildDetailsMap(Object rawDetails) { + Map resultMap = new HashMap<>(); + + JsonNode rootNode = toJsonNode(rawDetails); + if (rootNode == null || rootNode.isNull() || !rootNode.isObject()) { + log.warn("workingDaysDetails could not be parsed as an object, type: {}", + rawDetails == null ? "NULL" : rawDetails.getClass().getName()); + return resultMap; + } + + // Outer loop: year keys e.g. "2025", "2026" + rootNode.fields().forEachRemaining(yearEntry -> { + String year = yearEntry.getKey(); + JsonNode monthMapNode = yearEntry.getValue(); + if (!monthMapNode.isObject()) + return; + + // Inner loop: month keys e.g. "January", "February" + monthMapNode.fields().forEachRemaining(monthEntry -> { + JsonNode fd = monthEntry.getValue(); + if (!fd.isObject()) + return; + + String month = fd.hasNonNull("month") + ? fd.path("month").asText(null) + : monthEntry.getKey(); + + if (month == null) { + log.warn("Skipping detail with null month for year: {}", year); + return; + } + + Integer workingDays = fd.hasNonNull("workingDays") + ? fd.path("workingDays").asInt() + : null; + + WorkingDaysDetailsEntity detail = WorkingDaysDetailsEntity.builder() + .year(fd.hasNonNull("year") ? fd.path("year").asText(null) : year) + .month(month) + .workingDays(workingDays) + .build(); + + resultMap.put(month, detail); + log.debug("Added detail: year={}, month={}, workingDays={}", year, month, workingDays); + }); + }); + + return resultMap; + } + + /** + * Converts ObjectNode, Map, or JSON String → JsonNode + */ + private JsonNode toJsonNode(Object rawDetails) { + if (rawDetails == null) + return null; + + if (rawDetails instanceof JsonNode) { + return (JsonNode) rawDetails; + } + + if (rawDetails instanceof String) { + try { + return objectMapper.readTree((String) rawDetails); + } catch (Exception e) { + log.error("Failed to parse workingDaysDetails JSON string: {}", e.getMessage()); + return null; + } + } + + if (rawDetails instanceof Map) { + return objectMapper.valueToTree(rawDetails); + } + + log.warn("Unrecognized workingDaysDetails type: {}", rawDetails.getClass().getName()); + return null; + } + + // --- Utility Helpers --- + + private UUID parseUUID(Object value) { + if (value == null) + return null; + try { + return UUID.fromString(value.toString()); + } catch (Exception e) { + log.warn("Failed to parse UUID from value: {}", value); + return null; + } + } +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/DashboardServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/DashboardServiceImpl.java new file mode 100644 index 0000000..3670499 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/DashboardServiceImpl.java @@ -0,0 +1,35 @@ +package com.ikon.projectmanagement.service.impl; + +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.ikon.projectmanagement.dto.response.DashboardWidgetsResponseDto; +import com.ikon.projectmanagement.dto.response.StatusWiseProjectResponseData; +import com.ikon.projectmanagement.repository.IssueRepository; +import com.ikon.projectmanagement.repository.ProjectRepository; +import com.ikon.projectmanagement.repository.RiskRepository; +import com.ikon.projectmanagement.service.DashboardService; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class DashboardServiceImpl implements DashboardService { + private final ProjectRepository projectRepository; + private final IssueRepository issueRepository; + private final RiskRepository riskRepository; + + @Override + public DashboardWidgetsResponseDto getDashboardWidgetsData() { + DashboardWidgetsResponseDto widgetsData = new DashboardWidgetsResponseDto(); + widgetsData.setTotalProjects(projectRepository.count()); + widgetsData.setTotalIssues(issueRepository.count()); + widgetsData.setTotalRisksCount(riskRepository.count()); + return widgetsData; + } + + @Override + public List getStatusWiseProjectData() { + return projectRepository.getProjectStatusCounts(); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/EmployeeServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/EmployeeServiceImpl.java new file mode 100644 index 0000000..fd30bfb --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/EmployeeServiceImpl.java @@ -0,0 +1,61 @@ +package com.ikon.projectmanagement.service.impl; + +import com.ikon.projectmanagement.dto.request.EmployeeRequestDto; +import com.ikon.projectmanagement.dto.response.EmployeeResponseDto; +import com.ikon.projectmanagement.entity.Employee; +import com.ikon.projectmanagement.mapper.EmployeeMapper; +import com.ikon.projectmanagement.repository.EmployeeRepository; +import com.ikon.projectmanagement.service.EmployeeService; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class EmployeeServiceImpl implements EmployeeService { + + private final EmployeeRepository employeeRepository; + private final EmployeeMapper employeeMapper; + + @Override + public List getAllEmployees() { + return employeeRepository.findAll().stream() + .map(employeeMapper::toResponseDto) + .collect(Collectors.toList()); + } + + @Override + public EmployeeResponseDto getEmployeeById(String id) { + Employee employee = employeeRepository.findByEmpId(id) + .orElseThrow(() -> new RuntimeException("Employee not found with ID: " + id)); + return employeeMapper.toResponseDto(employee); + } + + @Override + public EmployeeResponseDto createEmployee(EmployeeRequestDto dto) { + // Check if employee already exists + if (dto.getEmpId() != null && employeeRepository.findByEmpId(dto.getEmpId()).isPresent()) { + throw new RuntimeException("Employee already exists with ID: " + dto.getEmpId()); + } + Employee employee = employeeMapper.toEntity(dto); + Employee savedEmployee = employeeRepository.save(employee); + return employeeMapper.toResponseDto(savedEmployee); + } + + @Override + public EmployeeResponseDto updateEmployee(String id, EmployeeRequestDto dto) { + Employee employee = employeeRepository.findByEmpId(id) + .orElseThrow(() -> new RuntimeException("Employee not found with ID: " + id)); + employeeMapper.updateEntity(employee, dto); + Employee updatedEmployee = employeeRepository.save(employee); + return employeeMapper.toResponseDto(updatedEmployee); + } + + @Override + public void deleteEmployee(String id) { + Employee employee = employeeRepository.findByEmpId(id) + .orElseThrow(() -> new RuntimeException("Employee not found with ID: " + id)); + employeeRepository.delete(employee); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/FxRateServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/FxRateServiceImpl.java new file mode 100644 index 0000000..52610bf --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/FxRateServiceImpl.java @@ -0,0 +1,64 @@ +package com.ikon.projectmanagement.service.impl; + +import java.util.HashMap; +import java.util.Map; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import com.ikon.projectmanagement.dto.response.FxRateDetailsDto; +import com.ikon.projectmanagement.dto.response.FxRateResponseDto; +import com.ikon.projectmanagement.entity.FxRateEntity; +import com.ikon.projectmanagement.repository.FxRateRepository; +import com.ikon.projectmanagement.service.FxRateService; +import com.ikon.webservice.WebService; +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class FxRateServiceImpl extends WebService implements FxRateService { + + private final FxRateRepository fxRateRepository; + + @Override + public Page getAllFxRates(Pageable pageable) { + return fxRateRepository.findAll(pageable) + .map(this::mapToResponseDto); + } + + @Override + public FxRateResponseDto getFxRateByYear(String year) { + FxRateEntity entity = fxRateRepository.findByYear(year) + .orElseThrow(() -> new EntityNotFoundException( + "FX rate not found for year: " + year)); + + return mapToResponseDto(entity); + } + + // ------------------------------------------------------------------------- + // Private helpers + // ------------------------------------------------------------------------ + + private FxRateResponseDto mapToResponseDto(FxRateEntity entity) { + Map> detailsMap = new HashMap<>(); + + entity.getFxRateDetails().values().forEach(detail -> { + FxRateDetailsDto dto = new FxRateDetailsDto(); + dto.setId(detail.getCurrencyId()); + dto.setCurrency(detail.getCurrency()); + dto.setFxRate(detail.getFxRate()); + dto.setActiveStatus(detail.getActiveStatus()); + dto.setYear(detail.getYear()); + + detailsMap + .computeIfAbsent(detail.getYear(), y -> new HashMap<>()) + .put(detail.getCurrencyId(), dto); + }); + + FxRateResponseDto responseDto = new FxRateResponseDto(); + responseDto.setFxRateId(entity.getFxRateId()); + responseDto.setAccountIdentifier(entity.getAccountIdentifier()); + responseDto.setFxRateDetails(detailsMap); + return responseDto; + } +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/GradeServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/GradeServiceImpl.java new file mode 100644 index 0000000..099e8b9 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/GradeServiceImpl.java @@ -0,0 +1,58 @@ +package com.ikon.projectmanagement.service.impl; + +import com.ikon.projectmanagement.dto.request.GradeRequestDto; +import com.ikon.projectmanagement.dto.response.GradeResponseDto; +import com.ikon.projectmanagement.entity.Grade; +import com.ikon.projectmanagement.mapper.GradeMapper; +import com.ikon.projectmanagement.repository.GradeRepository; +import com.ikon.projectmanagement.service.GradeService; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class GradeServiceImpl implements GradeService { + + private final GradeRepository gradeRepository; + private final GradeMapper gradeMapper; + + @Override + public List getAllGrades() { + return gradeRepository.findAll().stream() + .map(gradeMapper::toResponseDto) + .collect(Collectors.toList()); + } + + @Override + public GradeResponseDto getGradeById(UUID id) { + Grade grade = gradeRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Grade not found with ID: " + id)); + return gradeMapper.toResponseDto(grade); + } + + @Override + public GradeResponseDto createGrade(GradeRequestDto dto) { + Grade grade = gradeMapper.toEntity(dto); + Grade savedGrade = gradeRepository.save(grade); + return gradeMapper.toResponseDto(savedGrade); + } + + @Override + public GradeResponseDto updateGrade(UUID id, GradeRequestDto dto) { + Grade grade = gradeRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Grade not found with ID: " + id)); + gradeMapper.updateEntity(grade, dto); + Grade updatedGrade = gradeRepository.save(grade); + return gradeMapper.toResponseDto(updatedGrade); + } + + @Override + public void deleteGrade(UUID id) { + Grade grade = gradeRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Grade not found with ID: " + id)); + gradeRepository.delete(grade); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ImportEmployeeServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ImportEmployeeServiceImpl.java new file mode 100644 index 0000000..a3f8227 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ImportEmployeeServiceImpl.java @@ -0,0 +1,132 @@ +package com.ikon.projectmanagement.service.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import com.ikon.projectmanagement.dto.response.EmployeeResponseDto; +import com.ikon.projectmanagement.dto.request.EmployeeRequestDto; +import com.ikon.projectmanagement.dto.response.EmployeeResponseDto; +import com.ikon.projectmanagement.entity.Employee; +import com.ikon.projectmanagement.repository.EmployeeRepository; +import com.ikon.projectmanagement.mapper.EmployeeMapper; +import com.ikon.projectmanagement.service.ImportEmployeeService; +import com.ikon.webservice.WebService; + +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.UUID; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ImportEmployeeServiceImpl extends WebService implements ImportEmployeeService { + + private final EmployeeMapper employeeMapper; + + private final RestTemplate restTemplate; + private final EmployeeRepository employeeRepository; + + @Value("${salescrm.api.base-url}") + private String salesCrmBaseUrl; + + private HttpHeaders createHeaders(String accessToken) { + HttpHeaders headers = new HttpHeaders(); + if (accessToken != null && !accessToken.isEmpty()) { + headers.set("Authorization", accessToken); + } + headers.set("Content-Type", "application/json"); + return headers; + } + + private static final int PAGE_SIZE = 200; + + @Override + @Transactional + public List importEmployees(String accessToken) { + String baseUrl = salesCrmBaseUrl + "/employee"; + UUID accountId = getActiveAccountId(); + log.info("Fetching all employees from Sales CRM: {}", baseUrl); + try { + HttpEntity entity = new HttpEntity<>(createHeaders(accessToken)); + List allDtos = new ArrayList<>(); + List savedResponses = new ArrayList<>(); + int page = 0; + int totalPages = 1; + + while (page < totalPages) { + String url = baseUrl + "?page=" + page + "&size=" + PAGE_SIZE; + ResponseEntity> response = restTemplate.exchange( + url, HttpMethod.GET, entity, new ParameterizedTypeReference>() {}); + + if (response.getBody() == null || response.getBody().getContent() == null) { + break; + } + + PageResponse body = response.getBody(); + allDtos.addAll(body.getContent()); + totalPages = body.getTotalPages(); + log.info("Fetched employees page {}/{}", page + 1, totalPages); + page++; + } + + for (EmployeeRequestDto dto : allDtos) { + Optional existingOpt = employeeRepository.findByEmpId(dto.getEmpId()); + Employee emp; + if (existingOpt.isPresent()) { + emp = existingOpt.get(); + emp.setAccountId(accountId); + emp.setName(dto.getName()); + emp.setEmail(dto.getEmail()); + emp.setOrganizationEmail(dto.getOrganizationEmail()); + emp.setRole(dto.getRole()); + emp.setGrade(dto.getGrade()); + emp.setActive(dto.getActive()); + } else { + emp = Employee.builder() + .empId(dto.getEmpId()) + .accountId(accountId) + .name(dto.getName()) + .email(dto.getEmail()) + .organizationEmail(dto.getOrganizationEmail()) + .role(dto.getRole()) + .grade(dto.getGrade()) + .active(dto.getActive()) + .build(); + } + employeeRepository.save(emp); + savedResponses.add(employeeMapper.toResponseDto(emp)); + } + + if (!allDtos.isEmpty()) { + log.info("Saved/Updated {} employees in total", allDtos.size()); + } + + return savedResponses; + } catch (RestClientException e) { + log.error("Failed to fetch employees from Sales CRM: {}", e.getMessage()); + return Collections.emptyList(); + } + } + + @Data + public static class PageResponse { + private List content; + private int totalPages; + private long totalElements; + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ImportFxRateServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ImportFxRateServiceImpl.java new file mode 100644 index 0000000..289a093 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ImportFxRateServiceImpl.java @@ -0,0 +1,141 @@ +package com.ikon.projectmanagement.service.impl; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import com.ikon.projectmanagement.dto.response.FxRateDto; +import com.ikon.projectmanagement.entity.FxRateEntity; +import com.ikon.projectmanagement.entity.FxRateEntity; +import com.ikon.projectmanagement.repository.FxRateRepository; +import com.ikon.projectmanagement.service.ImportFxRateService; +import com.ikon.webservice.WebService; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ImportFxRateServiceImpl extends WebService implements ImportFxRateService { + + private final RestTemplate restTemplate; + private final FxRateRepository fxRateRepository; + + @Value("${salescrm.api.base-url}") + private String salesCrmBaseUrl; + + private HttpHeaders createHeaders(String accessToken) { + HttpHeaders headers = new HttpHeaders(); + if (accessToken != null && !accessToken.isEmpty()) { + headers.set("Authorization", accessToken); + } + headers.set("Content-Type", "application/json"); + return headers; + } + + private static final int PAGE_SIZE = 200; + + @Override + @Transactional + public List importFxRates(String accessToken) { + String baseUrl = salesCrmBaseUrl + "/fxrate"; + log.info("Fetching all FX rates from Sales CRM: {}", baseUrl); + try { + HttpEntity entity = new HttpEntity<>(createHeaders(accessToken)); + List allRaw = new ArrayList<>(); + int page = 0; + int totalPages = 1; + + while (page < totalPages) { + String url = baseUrl + "?page=" + page + "&size=" + PAGE_SIZE; + ResponseEntity> response = restTemplate.exchange( + url, HttpMethod.GET, entity, + new ParameterizedTypeReference>() {}); + + if (response.getBody() == null || response.getBody().getContent() == null) { + break; + } + + PageResponse body = response.getBody(); + allRaw.addAll(body.getContent()); + totalPages = body.getTotalPages(); + log.info("Fetched FX rates page {}/{}", page + 1, totalPages); + page++; + } + + List dtos = allRaw.stream() + .map(this::mapToFxRateDto) + .collect(Collectors.toList()); + + List entities = dtos.stream().map(dto -> FxRateEntity.builder() + .fxRateId(dto.getId()) + .accountIdentifier(dto.getAccountId()) + .build()).collect(Collectors.toList()); + + if (!entities.isEmpty()) { + fxRateRepository.saveAll(entities); + log.info("Saved/Updated {} FX rates in total", entities.size()); + } + + return dtos; + } catch (RestClientException e) { + log.error("Failed to fetch FX rates from Sales CRM: {}", e.getMessage()); + return Collections.emptyList(); + } + } + + private FxRateDto mapToFxRateDto(SalesCrmFxRateDto source) { + BigDecimal rateValue = BigDecimal.ZERO; + if (source.getRate() != null) { + try { + rateValue = new BigDecimal(source.getRate()); + } catch (NumberFormatException e) { + log.warn("Invalid rate format for id {}: {}", source.getId(), source.getRate()); + } + } + return FxRateDto.builder() + .id(source.getId()) + .accountId(source.getAccountId()) + .toCurrency(source.getCurrency()) + .fromCurrency("USD") + .rate(rateValue) + .effectiveDate(source.getYear()) + .build(); + } + + @Data + public static class PageResponse { + private List content; + private int totalPages; + private long totalElements; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + private static class SalesCrmFxRateDto { + private UUID id; + private UUID accountId; + private String year; + private String rate; + private String currency; + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ImportGradeServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ImportGradeServiceImpl.java new file mode 100644 index 0000000..0a1a91c --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ImportGradeServiceImpl.java @@ -0,0 +1,85 @@ +package com.ikon.projectmanagement.service.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import com.ikon.projectmanagement.dto.request.GradeRequestDto; +import com.ikon.projectmanagement.dto.response.GradeResponseDto; +import com.ikon.projectmanagement.entity.Grade; +import com.ikon.projectmanagement.mapper.GradeMapper; +import com.ikon.projectmanagement.repository.GradeRepository; +import com.ikon.projectmanagement.service.ImportGradeService; +import com.ikon.webservice.WebService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.UUID; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ImportGradeServiceImpl extends WebService implements ImportGradeService { + + private final GradeMapper gradeMapper; + + private final RestTemplate restTemplate; + private final GradeRepository gradeRepository; + + @Value("${salescrm.api.base-url}") + private String salesCrmBaseUrl; + + private HttpHeaders createHeaders(String accessToken) { + HttpHeaders headers = new HttpHeaders(); + if (accessToken != null && !accessToken.isEmpty()) { + headers.set("Authorization", accessToken); + } + headers.set("Content-Type", "application/json"); + return headers; + } + + @Override + @Transactional + public List importGrades(String accessToken) { + String url = salesCrmBaseUrl + "/grade"; + UUID accountId = getActiveAccountId(); + log.info("Fetching all grades from Sales CRM: {}", url); + try { + HttpEntity entity = new HttpEntity<>(createHeaders(accessToken)); + ResponseEntity> response = restTemplate.exchange( + url, HttpMethod.GET, entity, new ParameterizedTypeReference>() {}); + List dtos = response.getBody() != null ? response.getBody() : Collections.emptyList(); + List savedResponses = new ArrayList<>(); + + List entities = dtos.stream().map(dto -> Grade.builder() + .id(dto.getId()) + .accountId(accountId) + .grade(dto.getGrade()) + .build()).collect(Collectors.toList()); + + if (!entities.isEmpty()) { + gradeRepository.saveAll(entities); + log.info("Saved/Updated {} grades", entities.size()); + } + + entities.forEach(g -> savedResponses.add(gradeMapper.toResponseDto(g))); + return savedResponses; + } catch (RestClientException e) { + log.error("Failed to fetch grades from Sales CRM: {}", e.getMessage()); + return Collections.emptyList(); + } + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ImportRoleServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ImportRoleServiceImpl.java new file mode 100644 index 0000000..05ed82e --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ImportRoleServiceImpl.java @@ -0,0 +1,85 @@ +package com.ikon.projectmanagement.service.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import com.ikon.projectmanagement.dto.request.RoleRequestDto; +import com.ikon.projectmanagement.dto.response.RoleResponseDto; +import com.ikon.projectmanagement.entity.Role; +import com.ikon.projectmanagement.mapper.RoleMapper; +import com.ikon.projectmanagement.repository.RoleRepository; +import com.ikon.projectmanagement.service.ImportRoleService; +import com.ikon.webservice.WebService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.UUID; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ImportRoleServiceImpl extends WebService implements ImportRoleService { + + private final RoleMapper roleMapper; + + private final RestTemplate restTemplate; + private final RoleRepository roleRepository; + + @Value("${salescrm.api.base-url}") + private String salesCrmBaseUrl; + + private HttpHeaders createHeaders(String accessToken) { + HttpHeaders headers = new HttpHeaders(); + if (accessToken != null && !accessToken.isEmpty()) { + headers.set("Authorization", accessToken); + } + headers.set("Content-Type", "application/json"); + return headers; + } + + @Override + @Transactional + public List importRoles(String accessToken) { + String url = salesCrmBaseUrl + "/crm-role"; + UUID accountId = getActiveAccountId(); + log.info("Fetching all roles from Sales CRM: {}", url); + try { + HttpEntity entity = new HttpEntity<>(createHeaders(accessToken)); + ResponseEntity> response = restTemplate.exchange( + url, HttpMethod.GET, entity, new ParameterizedTypeReference>() {}); + List dtos = response.getBody() != null ? response.getBody() : Collections.emptyList(); + List savedResponses = new ArrayList<>(); + + List entities = dtos.stream().map(dto -> Role.builder() + .id(dto.getId()) + .accountId(accountId) + .role(dto.getRole()) + .build()).collect(Collectors.toList()); + + if (!entities.isEmpty()) { + roleRepository.saveAll(entities); + log.info("Saved/Updated {} roles", entities.size()); + } + + entities.forEach(r -> savedResponses.add(roleMapper.toResponseDto(r))); + return savedResponses; + } catch (RestClientException e) { + log.error("Failed to fetch roles from Sales CRM: {}", e.getMessage()); + return Collections.emptyList(); + } + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ImportWorkingDayServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ImportWorkingDayServiceImpl.java new file mode 100644 index 0000000..50acc93 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ImportWorkingDayServiceImpl.java @@ -0,0 +1,109 @@ +package com.ikon.projectmanagement.service.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import com.ikon.projectmanagement.dto.response.WorkingDayDto; +import com.ikon.projectmanagement.entity.WorkingDaysEntity; +import com.ikon.projectmanagement.repository.WorkingDaysRepository; +import com.ikon.projectmanagement.service.ImportWorkingDayService; +import com.ikon.webservice.WebService; + +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.UUID; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ImportWorkingDayServiceImpl extends WebService implements ImportWorkingDayService { + + private final RestTemplate restTemplate; + private final WorkingDaysRepository workingDayRepository; + + @Value("${salescrm.api.base-url}") + private String salesCrmBaseUrl; + + private HttpHeaders createHeaders(String accessToken) { + HttpHeaders headers = new HttpHeaders(); + if (accessToken != null && !accessToken.isEmpty()) { + headers.set("Authorization", accessToken); + } + headers.set("Content-Type", "application/json"); + return headers; + } + + private static final int PAGE_SIZE = 200; + + @Override + @Transactional + public List importWorkingDays(String accessToken) { + String baseUrl = salesCrmBaseUrl + "/workingday"; + UUID accountId = getActiveAccountId(); + log.info("Fetching all working days from Sales CRM: {}", baseUrl); + try { + HttpEntity entity = new HttpEntity<>(createHeaders(accessToken)); + List allDtos = new ArrayList<>(); + int page = 0; + int totalPages = 1; + + while (page < totalPages) { + String url = baseUrl + "?page=" + page + "&size=" + PAGE_SIZE; + ResponseEntity> response = restTemplate.exchange( + url, HttpMethod.GET, entity, new ParameterizedTypeReference>() { + }); + + if (response.getBody() == null || response.getBody().getContent() == null) { + break; + } + + PageResponse body = response.getBody(); + allDtos.addAll(body.getContent()); + totalPages = body.getTotalPages(); + log.info("Fetched working days page {}/{}", page + 1, totalPages); + page++; + } + + // List entities = allDtos.stream().map(dto -> + // WorkingDaysEntity.builder() + // .id(dto.getId()) + // .accountId(accountId) + // .year(dto.getYear()) + // .month(dto.getMonth()) + // .workingDays(dto.getWorkingDays()) + // .build()).collect(Collectors.toList()); + + // if (!entities.isEmpty()) { + // workingDayRepository.saveAll(entities); + // log.info("Saved/Updated {} working days in total", entities.size()); + // } + + return allDtos; + } catch (RestClientException e) { + log.error("Failed to fetch working days from Sales CRM: {}", e.getMessage()); + return Collections.emptyList(); + } + } + + @Data + public static class PageResponse { + private List content; + private int totalPages; + private long totalElements; + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/IssueServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/IssueServiceImpl.java new file mode 100644 index 0000000..dc9a579 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/IssueServiceImpl.java @@ -0,0 +1,112 @@ +package com.ikon.projectmanagement.service.impl; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + +import com.ikon.projectmanagement.dto.request.IssueCreateRequestDto; +import com.ikon.projectmanagement.dto.response.IssueResponseDto; +import com.ikon.projectmanagement.entity.Issue; +import com.ikon.projectmanagement.mapper.IssueMapper; +import com.ikon.projectmanagement.repository.IssueRepository; +import com.ikon.projectmanagement.repository.ProjectRepository; +import com.ikon.projectmanagement.service.IssueService; +import com.ikon.webservice.WebService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import org.apache.kafka.common.errors.ResourceNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class IssueServiceImpl extends WebService implements IssueService { + + private final IssueRepository issueRepository; + private final ProjectRepository projectRepository; + private final IssueMapper issueMapper; + + @Override + @Transactional + public IssueResponseDto createIssue(IssueCreateRequestDto issueCreateRequestDto) { + + log.info("Creating issue for project: {}", issueCreateRequestDto.getProjectIdentifier()); + + UUID projectId = issueCreateRequestDto.getProjectIdentifier(); + + projectRepository.findByProjectIdentifier(projectId) + .orElseThrow(() -> new NoSuchElementException( + "Project not found with identifier: " + issueCreateRequestDto.getProjectIdentifier())); + + Issue issue = issueMapper.toEntity(issueCreateRequestDto); + issue.setProjectIdentifier(projectId); + issue.setIssueStatus("Active"); + + Issue savedIssue = issueRepository.save(issue); + + return issueMapper.toResponse(savedIssue); + } + + @Override + public List getAllIssues(UUID projectIdentifier) { + + log.info("Fetching all issues"); + + return issueRepository.findByProjectIdentifier(projectIdentifier) + .stream() + .map(issueMapper::toResponse) + .toList(); + } + + @Override + public IssueResponseDto getIssueById(UUID issueIdentifier) { + + log.info("Fetching issue with identifier: {}", issueIdentifier); + + Issue issue = issueRepository.findById(issueIdentifier) + .orElseThrow(() -> new NoSuchElementException( + "Issue not found with identifier: " + issueIdentifier)); + + return issueMapper.toResponse(issue); + } + + @Override + @Transactional + public IssueResponseDto updateIssue(UUID issueIdentifier, IssueCreateRequestDto issueCreateRequestDto) { + + log.info("Updating issue with identifier: {}", issueIdentifier); + + Issue existingIssue = issueRepository.findById(issueIdentifier) + .orElseThrow(() -> new ResourceNotFoundException( + "Issue not found with identifier: " + issueIdentifier)); + + issueMapper.updateEntityFromDto(issueCreateRequestDto, existingIssue); + + Issue updatedIssue = issueRepository.save(existingIssue); + + log.info("Issue updated successfully with identifier: {}", issueIdentifier); + + return issueMapper.toResponse(updatedIssue); + } + + @Override + public List getIssuesByProject(String projectIdentifier) { + + log.info("Fetching all issues for project: {}", projectIdentifier); + + UUID projectId = UUID.fromString(projectIdentifier); + + // Validate project exists + projectRepository.findByProjectIdentifier(projectId) + .orElseThrow(() -> new NoSuchElementException( + "Project not found with identifier: " + projectIdentifier)); + + return issueRepository.findByProjectIdentifier(projectId) + .stream() + .map(issueMapper::toResponse) + .toList(); + } +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/MeetingServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/MeetingServiceImpl.java new file mode 100644 index 0000000..41a1fb0 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/MeetingServiceImpl.java @@ -0,0 +1,98 @@ +package com.ikon.projectmanagement.service.impl; + +import com.ikon.projectmanagement.dto.request.MeetingRequestDTO; +import com.ikon.projectmanagement.dto.response.MeetingResponseDTO; +import com.ikon.projectmanagement.entity.Meeting; +import com.ikon.projectmanagement.mapper.MeetingMapper; +import com.ikon.projectmanagement.repository.MeetingRepository; +import com.ikon.projectmanagement.service.MeetingService; +import com.ikon.webservice.WebService; + +import lombok.RequiredArgsConstructor; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class MeetingServiceImpl extends WebService implements MeetingService { + + @Autowired + private MeetingMapper meetingMapper; + + @Autowired + private MeetingRepository meetingRepository; + + @Override + @Transactional + public MeetingResponseDTO createMeeting(MeetingRequestDTO meetingRequestDTO) { + + UUID accountId = getActiveAccountId(); + UUID createdBy = getCurrentUserId(); + + Meeting meeting = meetingMapper.toEntity(meetingRequestDTO); + + meeting.setAccountId(accountId); + meeting.setCreatedBy(createdBy); + + Meeting savedMeeting = meetingRepository.save(meeting); + + return meetingMapper.toResponseDTO(savedMeeting); + } + + @Override + @Transactional(readOnly = true) + public List getAllMeetings(UUID projectIdentifier) { + + List meetings = meetingRepository.findByProjectIdentifier(projectIdentifier); + + return meetingMapper.toResponseDTOList(meetings); + } + + @Override + @Transactional(readOnly = true) + public List getMeetingsByProjectIdentifier(String projectIdentifier) { + + List meetings = meetingRepository.findByProjectIdentifier(UUID.fromString(projectIdentifier)); + + return meetingMapper.toResponseDTOList(meetings); + } + + @Override + @Transactional(readOnly = true) + public MeetingResponseDTO getMeetingById(UUID meetingId) { + + Meeting meeting = meetingRepository.findById(meetingId) + .orElseThrow(() -> new RuntimeException("Meeting not found with id: " + meetingId)); + + return meetingMapper.toResponseDTO(meeting); + } + + @Override + @Transactional + public MeetingResponseDTO updateMeeting(UUID meetingId, MeetingRequestDTO meetingRequestDTO) { + + Meeting existingMeeting = meetingRepository.findById(meetingId) + .orElseThrow(() -> new RuntimeException("Meeting not found with id: " + meetingId)); + + meetingMapper.updateEntityFromDTO(existingMeeting, meetingRequestDTO); + + Meeting updatedMeeting = meetingRepository.save(existingMeeting); + + return meetingMapper.toResponseDTO(updatedMeeting); + } + + @Override + @Transactional + public void deleteMeeting(UUID meetingId) { + + Meeting meeting = meetingRepository.findById(meetingId) + .orElseThrow(() -> new RuntimeException("Meeting not found with id: " + meetingId)); + + meetingRepository.delete(meeting); + } +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ProductOfProjectServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ProductOfProjectServiceImpl.java new file mode 100644 index 0000000..8581a4d --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ProductOfProjectServiceImpl.java @@ -0,0 +1,213 @@ +package com.ikon.projectmanagement.service.impl; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.ikon.projectmanagement.dto.request.ProductOfProjectRequestDto; +import com.ikon.projectmanagement.dto.response.ProductExpenseResponseDto; +import com.ikon.projectmanagement.dto.response.ProductOfProjectResponseDto; +import com.ikon.projectmanagement.entity.ProductOfProject; +import com.ikon.projectmanagement.entity.ProductOfProjectExpenseDetail; +import com.ikon.projectmanagement.mapper.ProductOfProjectMapper; +import com.ikon.projectmanagement.repository.ProductOfProjectRepository; +import com.ikon.projectmanagement.repository.ProjectScheduleRepository; +import com.ikon.projectmanagement.service.ProductOfProjectService; +import com.ikon.projectmanagement.service.ScheduleService; +import com.ikon.webservice.WebService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ProductOfProjectServiceImpl extends WebService implements ProductOfProjectService { + + private final ProductOfProjectMapper productMapper; + private final ProductOfProjectRepository productRepository; + private final ScheduleService scheduleService; + private final ProjectScheduleRepository projectScheduleRepository; + + // Valid workflow transitions: currentStatus -> Set of allowed targetStatuses + private static final Map> VALID_TRANSITIONS = Map.of( + // Forward transitions + "Product Created", Set.of("Schedule Submitted From Product"), + "Schedule Submitted From Product", Set.of("Submitted Resources and Expenses For Product", + "Recall Schedule from Resource & Expense"), + // "Submitted Resources and Expenses For Product", Set.of("Closed", + // "Recall Schedule from Resource & Expense"), + // "Closed", Set.of("Recall Resources & Expenses from Closed"), + + // Also allow transitions from recall statuses + // "Recall Product Created from Schedule", Set.of("Schedule Submitted From + // Product"), + "Recall Schedule from Resource & Expense", Set.of("Schedule Submitted From Product") + // "Recall Resources & Expenses from Closed", Set.of("Closed", + // "Recall Schedule from Resource & Expense") + ); + + // The target status that requires schedule data to exist + private static final String REQUIRES_SCHEDULE = "Schedule Submitted From Product"; + + @Override + @Transactional(readOnly = true) + public List getProductsByProject(UUID projectIdentifier) { + + UUID accountId = getActiveAccountId(); + var scheduleData = scheduleService.getSchedule(projectIdentifier, null); + + return productRepository + .findByAccountIdAndProjectIdentifier(accountId, projectIdentifier) + .stream() + .map(p -> { + ProductOfProjectResponseDto dto = productMapper.toResponse(p); + dto.setScheduleData(scheduleData); + return dto; + }) + .toList(); + } + + @Override + @Transactional(readOnly = true) + public ProductOfProjectResponseDto getProduct(UUID projectIdentifier, UUID productIdentifier) { + + UUID accountId = getActiveAccountId(); + + ProductOfProject product = productRepository + .findByAccountIdAndProjectIdentifierAndProductIdentifier( + accountId, projectIdentifier, productIdentifier) + .orElseThrow(() -> new RuntimeException("Product not found")); + + ProductOfProjectResponseDto dto = productMapper.toResponse(product); + dto.setScheduleData(scheduleService.getSchedule(projectIdentifier, null)); + return dto; + } + + @Override + @Transactional + public ProductOfProjectResponseDto updateProduct(UUID projectIdentifier, UUID productIdentifier, + ProductOfProjectRequestDto dto) { + UUID accountId = getActiveAccountId(); + + ProductOfProject product = productRepository + .findByAccountIdAndProjectIdentifierAndProductIdentifier(accountId, projectIdentifier, + productIdentifier) + .orElseThrow(() -> new RuntimeException("Product not found")); + + productMapper.updateEntity(product, dto); + + if (dto.getExpenseDetails() != null) { + Map existingExpenses = product.getExpenseDetails(); + Map incomingExpenses = dto.getExpenseDetails(); + + existingExpenses.keySet().removeIf(id -> !incomingExpenses.containsKey(id)); + + for (Map.Entry entry : incomingExpenses.entrySet()) { + UUID incomingId = entry.getKey(); + ProductExpenseResponseDto expenseDto = entry.getValue(); + + if (incomingId != null && existingExpenses.containsKey(incomingId)) { + ProductOfProjectExpenseDetail existing = existingExpenses.get(incomingId); + productMapper.updateExpenseEntity(existing, expenseDto); + } else { + ProductOfProjectExpenseDetail newExpense = productMapper + .toExpenseEntity(expenseDto); + newExpense.setProductPS(product); + + existingExpenses.put(UUID.randomUUID(), newExpense); + } + } + } + + ProductOfProject saved = productRepository.save(product); + return productMapper.toResponse(saved); + } + + @Override + @Transactional + public ProductOfProjectResponseDto transitionStatus( + UUID projectIdentifier, + UUID productIdentifier, + String targetStatus) { + + UUID accountId = getActiveAccountId(); + + ProductOfProject product = productRepository + .findByAccountIdAndProjectIdentifierAndProductIdentifier( + accountId, projectIdentifier, productIdentifier) + .orElseThrow(() -> new RuntimeException("Product not found")); + + String currentStatus = product.getProductStatus(); + log.info("Workflow transition requested: '{}' -> '{}' for product {}", + currentStatus, targetStatus, productIdentifier); + + // Validate that this transition is allowed + Set allowedTargets = VALID_TRANSITIONS.get(currentStatus); + log.info("Allowed target statuses for current status '{}': {}", currentStatus, allowedTargets); + if (allowedTargets == null || !allowedTargets.contains(targetStatus)) { + throw new IllegalStateException( + "Transition from '" + currentStatus + "' to '" + targetStatus + + "' is not allowed."); + } + + // Special validation: Schedule -> Resources & Expenses requires schedule data + if (REQUIRES_SCHEDULE.equals(targetStatus)) { + boolean hasScheduleData = checkScheduleDataExists(projectIdentifier, productIdentifier, + accountId); + if (!hasScheduleData) { + throw new IllegalStateException( + "Cannot move to Resources & Expenses: No schedule data found. " + + "Please create a schedule before proceeding."); + } + } + + // Perform the transition + product.setProductStatus(targetStatus); + ProductOfProject saved = productRepository.save(product); + + log.info("Workflow transition completed: '{}' -> '{}' for product {}", + currentStatus, targetStatus, productIdentifier); + + return productMapper.toResponse(saved); + } + + /** + * Checks if schedule data exists by looking up: + * 1. Project schedule (by projectIdentifier) + * 2. Product schedule data (by productIdentifier — stored on the product + * entity) + * If either has tasks, returns true. + */ + private boolean checkScheduleDataExists(UUID projectIdentifier, UUID productIdentifier, UUID accountId) { + // Check 1: Project-level schedule + boolean hasProjectSchedule = projectScheduleRepository + .findByAccountIdAndProjectIdentifier(accountId, projectIdentifier) + .map(schedule -> schedule.getTasks() != null && !schedule.getTasks().isEmpty()) + .orElse(false); + + if (hasProjectSchedule) { + log.debug("Schedule data found via project schedule for project {}", projectIdentifier); + return true; + } + + // Check 2: Product-level schedule data + boolean hasProductSchedule = projectScheduleRepository + .findByAccountIdAndProductIdentifier(accountId, productIdentifier) + .map(schedule -> schedule.getTasks() != null && !schedule.getTasks().isEmpty()) + .orElse(false); + + if (hasProductSchedule) { + log.debug("Schedule data found via product schedule for product {}", productIdentifier); + return true; + } + + log.warn("No schedule data found for project {} or product {}", projectIdentifier, productIdentifier); + return false; + } + +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ProductResourceAllocationServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ProductResourceAllocationServiceImpl.java new file mode 100644 index 0000000..7ac638a --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ProductResourceAllocationServiceImpl.java @@ -0,0 +1,89 @@ +package com.ikon.projectmanagement.service.impl; + +import java.util.List; +import java.util.UUID; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.ikon.projectmanagement.dto.request.ProductPSResourceAllocationRequestDto; +import com.ikon.projectmanagement.dto.response.ProductPSResourceAllocationResponseDto; +import com.ikon.projectmanagement.entity.ProductOfProject; +import com.ikon.projectmanagement.entity.ProductPSResourceAllocation; +import com.ikon.projectmanagement.mapper.ProductResourceAllocationMapper; +import com.ikon.projectmanagement.repository.ProductOfProjectRepository; +import com.ikon.projectmanagement.service.ProductResourceAllocationService; +import com.ikon.webservice.WebService; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class ProductResourceAllocationServiceImpl extends WebService implements ProductResourceAllocationService { + + private final ProductOfProjectRepository productRepository; + private final ProductResourceAllocationMapper allocationMapper; + + private ProductOfProject getProduct(UUID productIdentifier) { + UUID accountId = getActiveAccountId(); + return productRepository + .findByAccountIdAndProductIdentifier(accountId, productIdentifier) + .orElseThrow(() -> new RuntimeException("Product not found")); + } + + @Override + @Transactional(readOnly = true) + public List getResourceAllocations(UUID productIdentifier) { + ProductOfProject product = getProduct(productIdentifier); + return product.getResourceDataWithAllocation().stream() + .map(allocationMapper::toResponse) + .toList(); + } + + @Override + @Transactional(readOnly = true) + public ProductPSResourceAllocationResponseDto getResourceAllocation(UUID productIdentifier, UUID allocationIdentifier) { + ProductOfProject product = getProduct(productIdentifier); + ProductPSResourceAllocation allocation = product.getResourceDataWithAllocation().stream() + .filter(a -> a.getId().equals(allocationIdentifier)) + .findFirst() + .orElseThrow(() -> new RuntimeException("Allocation not found")); + + return allocationMapper.toResponse(allocation); + } + + @Override + @Transactional + public ProductPSResourceAllocationResponseDto createResourceAllocation(UUID productIdentifier, ProductPSResourceAllocationRequestDto dto) { + ProductOfProject product = getProduct(productIdentifier); + ProductPSResourceAllocation allocation = allocationMapper.toEntity(dto, product); + product.getResourceDataWithAllocation().add(allocation); + productRepository.save(product); + return allocationMapper.toResponse(allocation); + } + + @Override + @Transactional + public ProductPSResourceAllocationResponseDto updateResourceAllocation(UUID productIdentifier, UUID allocationIdentifier, ProductPSResourceAllocationRequestDto dto) { + ProductOfProject product = getProduct(productIdentifier); + ProductPSResourceAllocation allocation = product.getResourceDataWithAllocation().stream() + .filter(a -> a.getId().equals(allocationIdentifier)) + .findFirst() + .orElseThrow(() -> new RuntimeException("Allocation not found")); + + allocationMapper.updateEntity(allocation, dto); + productRepository.save(product); + return allocationMapper.toResponse(allocation); + } + + @Override + @Transactional + public void deleteResourceAllocation(UUID productIdentifier, UUID allocationIdentifier) { + ProductOfProject product = getProduct(productIdentifier); + boolean removed = product.getResourceDataWithAllocation().removeIf(a -> a.getId().equals(allocationIdentifier)); + if (!removed) { + throw new RuntimeException("Allocation not found"); + } + productRepository.save(product); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ProjectServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ProjectServiceImpl.java new file mode 100644 index 0000000..b693a0c --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ProjectServiceImpl.java @@ -0,0 +1,139 @@ +package com.ikon.projectmanagement.service.impl; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + +import com.ikon.projectmanagement.dto.request.ProjectRequestDto; +import com.ikon.projectmanagement.dto.response.ProjectResponseDto; +import com.ikon.projectmanagement.dto.response.ProjectTimelineResponseDto; +import com.ikon.projectmanagement.entity.ProductOfProject; +import com.ikon.projectmanagement.entity.Project; +import com.ikon.projectmanagement.mapper.ProjectMapper; +import com.ikon.projectmanagement.repository.ProductOfProjectRepository; +import com.ikon.projectmanagement.repository.ProjectRepository; +import com.ikon.projectmanagement.service.ProjectService; +import com.ikon.webservice.WebService; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import org.apache.kafka.common.errors.ResourceNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ProjectServiceImpl extends WebService implements ProjectService { + + private final ProjectRepository projectRepository; + private final ProductOfProjectRepository productOfProjectRepository; + private final ProjectMapper projectMapper; + + // private final IkonServiceClient ikonServiceClient; + + @Override + @Transactional + public ProjectResponseDto createProject(ProjectRequestDto projectDto) { + + Project project = projectMapper.toEntity(projectDto); + + UUID accountId = getActiveAccountId(); + UUID createdById = getCurrentUserId(); + + String createdAt = LocalDateTime.now().toString(); + + project.setAccountId(accountId); + project.setProjectStatus("Ongoing"); + + Project savedProject = projectRepository.save(project); + + ProductOfProject product = new ProductOfProject(); + product.setAccountId(accountId); + product.setCreatedBy(createdById); + product.setCreatedOn(createdAt); + product.setProductType(projectDto.getProductType()); + product.setProductStatus("Product Created"); + + product.setProjectIdentifier(savedProject.getProjectIdentifier()); + product.setProjectName(savedProject.getProjectName()); + product.setProjectManager(savedProject.getProjectManager()); + + ProductOfProject savedProduct = productOfProjectRepository.save(product); + + savedProject.setProduct(savedProduct); + projectRepository.save(savedProject); + + return projectMapper.toResponse(savedProject); + } + + @Override + public List getAllProjects() { + + UUID activeAccountId = getActiveAccountId(); + + List projectList = projectRepository.findByAccountId(activeAccountId); + + return projectList.stream() + .map(projectMapper::toResponse) + .toList(); + } + + @Override + public ProjectResponseDto getProjectByProjectIdentifier(UUID projectIdentifier) { + Project project = projectRepository + .findByProjectIdentifier(projectIdentifier) + .orElseThrow(() -> new NoSuchElementException( + "Project not found with identifier: " + projectIdentifier)); + + return projectMapper.toResponse(project); + } + + @Override + @Transactional + public ProjectResponseDto updateProject(UUID projectIdentifier, ProjectRequestDto projectDto) { + + Project existingProject = projectRepository.findById(projectIdentifier) + .orElseThrow(() -> new ResourceNotFoundException( + "Project not found with id: " + projectIdentifier)); + + projectMapper.updateEntityFromDto(projectDto, existingProject); + + existingProject.setUpdatedOn(LocalDate.now()); + existingProject.setUpdatedBy(projectDto.getUpdatedBy()); + + Project updatedProject = projectRepository.save(existingProject); + + return projectMapper.toResponse(updatedProject); + } + + @Override + public List getAllActiveProjectsTimeline() { + // Projects are created with status "Ongoing" (see createProject), so the + // active-timeline must query that value — "ACTIVE" never matched anything. + List activeProjects = projectRepository + .findByProjectStatusIgnoreCase("Ongoing"); + + return activeProjects.stream() + .map(project -> ProjectTimelineResponseDto.builder() + .projectIdentifier( + project.getProjectIdentifier() != null + ? project.getProjectIdentifier() + : null) + .projectName(project.getProjectName()) + .startDate( + project.getContractedStartDate() != null + ? project.getContractedStartDate().toString() + : null) + .endDate( + project.getContractedEndDate() != null + ? project.getContractedEndDate().toString() + : null) + .build()) + .toList(); + } +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/RiskServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/RiskServiceImpl.java new file mode 100644 index 0000000..106b2e7 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/RiskServiceImpl.java @@ -0,0 +1,118 @@ +package com.ikon.projectmanagement.service.impl; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + +import com.ikon.projectmanagement.dto.request.RiskCreateRequestDto; +import com.ikon.projectmanagement.dto.response.RiskResponseDto; +import com.ikon.projectmanagement.entity.Risks; +import com.ikon.projectmanagement.mapper.RiskMapper; +import com.ikon.projectmanagement.repository.ProjectRepository; +import com.ikon.projectmanagement.repository.RiskRepository; +import com.ikon.projectmanagement.service.RiskService; +import com.ikon.webservice.WebService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import org.apache.kafka.common.errors.ResourceNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class RiskServiceImpl extends WebService implements RiskService { + + private final RiskRepository riskRepository; + private final ProjectRepository projectRepository; + private final RiskMapper riskMapper; + + @Override + @Transactional + public RiskResponseDto createRisk(RiskCreateRequestDto riskCreateRequestDto) { + + log.info("Creating risk for project: {}", riskCreateRequestDto.getProjectIdentifier()); + + // UUID projectId = UUID.fromString(riskCreateRequestDto.getProjectIdentifier()); + + UUID projectId = riskCreateRequestDto.getProjectIdentifier(); + + projectRepository.findByProjectIdentifier(projectId) + .orElseThrow(() -> new NoSuchElementException( + "Project not found with identifier: " + riskCreateRequestDto.getProjectIdentifier())); + + Risks risk = riskMapper.toEntity(riskCreateRequestDto); + // No more risk.setProject(project) — just set the UUID directly + // risk.setProjectIdentifier(projectId); + + // Risks savedRisk = riskRepository.saveAndFlush(risk); + + + Risks savedRisk = riskRepository.save(risk); + + return riskMapper.toResponse(savedRisk); + } + + @Override + public List getAllRisks() { + + log.info("Fetching all risks"); + + return riskRepository.findAll() + .stream() + .map(riskMapper::toResponse) + .toList(); + } + + @Override + public RiskResponseDto getRiskById(UUID riskIdentifier) { + + log.info("Fetching risk with identifier: {}", riskIdentifier); + + Risks risk = riskRepository.findById(riskIdentifier) + .orElseThrow(() -> new NoSuchElementException( + "Risk not found with identifier: " + riskIdentifier)); + + return riskMapper.toResponse(risk); + } + + @Override + @Transactional + public RiskResponseDto updateRisk(UUID riskIdentifier, RiskCreateRequestDto riskCreateRequestDto) { + + log.info("Updating risk with identifier: {}", riskIdentifier); + + Risks existingRisk = riskRepository.findById(riskIdentifier) + .orElseThrow(() -> new ResourceNotFoundException( + "Risk not found with identifier: " + riskIdentifier)); + + riskMapper.updateEntityFromDto(riskCreateRequestDto, existingRisk); + + Risks updatedRisk = riskRepository.save(existingRisk); + + log.info("Risk updated successfully with identifier: {}", riskIdentifier); + + return riskMapper.toResponse(updatedRisk); + } + + @Override + public List getRisksByProject(String projectIdentifier) { + + log.info("Fetching all risks for project: {}", projectIdentifier); + + UUID projectId = UUID.fromString(projectIdentifier); + + // Validate project exists + projectRepository.findByProjectIdentifier(projectId) + .orElseThrow(() -> new NoSuchElementException( + "Project not found with identifier: " + projectIdentifier)); + + // Updated to use direct field query instead of relationship navigation + return riskRepository.findByProjectIdentifier(projectId) + .stream() + .map(riskMapper::toResponse) + .toList(); + } +} \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/RoleServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/RoleServiceImpl.java new file mode 100644 index 0000000..3cbe501 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/RoleServiceImpl.java @@ -0,0 +1,58 @@ +package com.ikon.projectmanagement.service.impl; + +import com.ikon.projectmanagement.dto.request.RoleRequestDto; +import com.ikon.projectmanagement.dto.response.RoleResponseDto; +import com.ikon.projectmanagement.entity.Role; +import com.ikon.projectmanagement.mapper.RoleMapper; +import com.ikon.projectmanagement.repository.RoleRepository; +import com.ikon.projectmanagement.service.RoleService; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RoleServiceImpl implements RoleService { + + private final RoleRepository roleRepository; + private final RoleMapper roleMapper; + + @Override + public List getAllRoles() { + return roleRepository.findAll().stream() + .map(roleMapper::toResponseDto) + .collect(Collectors.toList()); + } + + @Override + public RoleResponseDto getRoleById(UUID id) { + Role role = roleRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Role not found with ID: " + id)); + return roleMapper.toResponseDto(role); + } + + @Override + public RoleResponseDto createRole(RoleRequestDto dto) { + Role role = roleMapper.toEntity(dto); + Role savedRole = roleRepository.save(role); + return roleMapper.toResponseDto(savedRole); + } + + @Override + public RoleResponseDto updateRole(UUID id, RoleRequestDto dto) { + Role role = roleRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Role not found with ID: " + id)); + roleMapper.updateEntity(role, dto); + Role updatedRole = roleRepository.save(role); + return roleMapper.toResponseDto(updatedRole); + } + + @Override + public void deleteRole(UUID id) { + Role role = roleRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Role not found with ID: " + id)); + roleRepository.delete(role); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ScheduleServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ScheduleServiceImpl.java new file mode 100644 index 0000000..310b426 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/ScheduleServiceImpl.java @@ -0,0 +1,127 @@ +package com.ikon.projectmanagement.service.impl; + +import com.ikon.projectmanagement.dto.request.ScheduleDependencyDto; +import com.ikon.projectmanagement.dto.request.ScheduleGroupDto; +import com.ikon.projectmanagement.dto.request.ScheduleRequestDto; +import com.ikon.projectmanagement.dto.request.ScheduleTaskDto; +import com.ikon.projectmanagement.dto.response.ScheduleResponseDto; +import com.ikon.projectmanagement.entity.ProductOfProject; +import com.ikon.projectmanagement.entity.ProjectSchedule; +import com.ikon.projectmanagement.mapper.ScheduleMapper; +import com.ikon.projectmanagement.repository.ProductOfProjectRepository; +import com.ikon.projectmanagement.repository.ProjectScheduleRepository; +import com.ikon.projectmanagement.service.ScheduleService; +import com.ikon.webservice.WebService; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ScheduleServiceImpl extends WebService implements ScheduleService { + + private final ProjectScheduleRepository scheduleRepository; + private final ProductOfProjectRepository productOfProjectRepository; + private final ScheduleMapper scheduleMapper; + + @PersistenceContext + private EntityManager entityManager; + + private ProjectSchedule getOrCreateSchedule(UUID projectIdentifier) { + UUID accountId = getActiveAccountId(); + return scheduleRepository + .findByAccountIdAndProjectIdentifier(accountId, projectIdentifier) + .orElseGet(() -> { + UUID productId = productOfProjectRepository + .findByAccountIdAndProjectIdentifier(accountId, projectIdentifier) + .stream().findFirst() + .map(ProductOfProject::getProductIdentifier) + .orElse(null); + ProjectSchedule newSchedule = ProjectSchedule.builder() + .projectIdentifier(projectIdentifier) + .productIdentifier(productId) + .accountId(accountId) + .build(); + return scheduleRepository.save(newSchedule); + }); + } + + @Override + @Transactional + public ScheduleResponseDto saveSchedule(UUID projectIdentifier, ScheduleRequestDto dto, String accessToken) { + log.debug("saveSchedule called for project: {}", projectIdentifier); + ProjectSchedule schedule = getOrCreateSchedule(projectIdentifier); + + // Clear existing data — orphanRemoval = true handles DB deletion + schedule.getTasks().clear(); + schedule.getDependencies().clear(); + schedule.getGroups().clear(); + + // Flush DELETEs first to avoid PK conflicts when re-inserting same IDs + entityManager.flush(); + + // Repopulate tasks + List tasks = dto.getTask(); + if (tasks != null) { + for (ScheduleTaskDto taskDto : tasks) { + schedule.getTasks().add(scheduleMapper.toTaskEntity(taskDto, schedule)); + } + } + + // Repopulate dependencies + List dependencies = dto.getDependency(); + if (dependencies != null) { + for (ScheduleDependencyDto depDto : dependencies) { + schedule.getDependencies().add(scheduleMapper.toDependencyEntity(depDto, schedule)); + } + } + + // Repopulate groups + Map groups = dto.getGroup(); + if (groups != null) { + for (Map.Entry entry : groups.entrySet()) { + schedule.getGroups().add(scheduleMapper.toGroupEntity(entry.getKey(), entry.getValue(), schedule)); + } + } + + ProjectSchedule saved = scheduleRepository.save(schedule); + log.debug("Schedule saved for project {}: {} tasks, {} dependencies, {} groups", + projectIdentifier, + saved.getTasks().size(), + saved.getDependencies().size(), + saved.getGroups().size()); + + return scheduleMapper.toResponse(saved); + } + + @Override + @Transactional(readOnly = true) + public ScheduleResponseDto getSchedule(UUID projectIdentifier, String accessToken) { + UUID accountId = getActiveAccountId(); + return scheduleRepository + .findByAccountIdAndProjectIdentifier(accountId, projectIdentifier) + .map(scheduleMapper::toResponse) + .orElse(new ScheduleResponseDto(projectIdentifier, null, new ArrayList<>(), new ArrayList<>(), new LinkedHashMap<>())); + } + + @Override + @Transactional(readOnly = true) + public List getAllSchedules() { + UUID accountId = getActiveAccountId(); + return scheduleRepository + .findAllByAccountId(accountId) + .stream() + .map(scheduleMapper::toResponse) + .toList(); + } +} diff --git a/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/WorkingDayServiceImpl.java b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/WorkingDayServiceImpl.java new file mode 100644 index 0000000..50675b7 --- /dev/null +++ b/backend/projectmanagement-server/src/main/java/com/ikon/projectmanagement/service/impl/WorkingDayServiceImpl.java @@ -0,0 +1,58 @@ +package com.ikon.projectmanagement.service.impl; + +import com.ikon.projectmanagement.dto.response.WorkingDaysMonthDto; +import com.ikon.projectmanagement.dto.response.WorkingDaysResponseDto; +import com.ikon.projectmanagement.entity.WorkingDaysEntity; +import com.ikon.projectmanagement.repository.WorkingDaysRepository; +import com.ikon.projectmanagement.service.WorkingDayService; +import jakarta.persistence.EntityNotFoundException; +import java.util.HashMap; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class WorkingDayServiceImpl implements WorkingDayService { + + private final WorkingDaysRepository workingDayRepository; + + @Override + public Page getAllWorkingDays(Pageable pageable) { + return workingDayRepository.findAll(pageable) + .map(this::mapToResponseDto); + } + + @Override + public WorkingDaysResponseDto getWorkingDaysByYear(String year) { + WorkingDaysEntity entity = workingDayRepository.findByYear(year) + .orElseThrow(() -> new EntityNotFoundException( + "Working days not found for year: " + year)); + + return mapToResponseDto(entity); + } + + private WorkingDaysResponseDto mapToResponseDto(WorkingDaysEntity entity) { + Map> detailsMap = new HashMap<>(); + + entity.getWorkingDaysDetails().values().forEach(detail -> { + WorkingDaysMonthDto monthDto = new WorkingDaysMonthDto(); + monthDto.setYear(detail.getYear()); + monthDto.setMonth(detail.getMonth()); + monthDto.setWorkingDays(detail.getWorkingDays()); + + // outer key = year, inner key = month + detailsMap + .computeIfAbsent(detail.getYear(), y -> new HashMap<>()) + .put(detail.getMonth(), monthDto); + }); + + WorkingDaysResponseDto responseDto = new WorkingDaysResponseDto(); + responseDto.setWorkingDaysDetails(detailsMap); + responseDto.setAccountIdentifier(entity.getAccountIdentifier()); + responseDto.setWorkingId(entity.getWorkingId()); + return responseDto; + } +} diff --git a/backend/projectmanagement-server/src/main/resources/activiti.engine.schema.sql b/backend/projectmanagement-server/src/main/resources/activiti.engine.schema.sql new file mode 100644 index 0000000..ce1dd9e --- /dev/null +++ b/backend/projectmanagement-server/src/main/resources/activiti.engine.schema.sql @@ -0,0 +1,1502 @@ +-- Modified version of script at (https://raw.githubusercontent.com/Activiti/Activiti/refs/tags/activiti-6.0.0/modules/activiti-engine/src/main/resources/org/activiti/db/create/activiti.mysql.create.engine.sql) + +start transaction; + +create table if not exists ACT_GE_PROPERTY ( + NAME_ varchar(64), + VALUE_ varchar(300), + REV_ integer, + primary key (NAME_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +insert ignore into ACT_GE_PROPERTY +values ('schema.version', '6.0.0.4', 1); + +insert ignore into ACT_GE_PROPERTY +values ('schema.history', 'create(6.0.0.4)', 1); + +insert ignore into ACT_GE_PROPERTY +values ('next.dbid', '1', 1); + +create table if not exists ACT_GE_BYTEARRAY ( + ID_ varchar(64), + REV_ integer, + NAME_ varchar(255), + DEPLOYMENT_ID_ varchar(64), + BYTES_ LONGBLOB, + GENERATED_ TINYINT, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_RE_DEPLOYMENT ( + ID_ varchar(64), + NAME_ varchar(255), + CATEGORY_ varchar(255), + KEY_ varchar(255), + TENANT_ID_ varchar(255) default '', + DEPLOY_TIME_ timestamp(3) NULL, + ENGINE_VERSION_ varchar(255), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_RE_MODEL ( + ID_ varchar(64) not null, + REV_ integer, + NAME_ varchar(255), + KEY_ varchar(255), + CATEGORY_ varchar(255), + CREATE_TIME_ timestamp(3) null, + LAST_UPDATE_TIME_ timestamp(3) null, + VERSION_ integer, + META_INFO_ varchar(4000), + DEPLOYMENT_ID_ varchar(64), + EDITOR_SOURCE_VALUE_ID_ varchar(64), + EDITOR_SOURCE_EXTRA_VALUE_ID_ varchar(64), + TENANT_ID_ varchar(255) default '', + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_RU_EXECUTION ( + ID_ varchar(64), + REV_ integer, + PROC_INST_ID_ varchar(64), + BUSINESS_KEY_ varchar(255), + PARENT_ID_ varchar(64), + PROC_DEF_ID_ varchar(64), + SUPER_EXEC_ varchar(64), + ROOT_PROC_INST_ID_ varchar(64), + ACT_ID_ varchar(255), + IS_ACTIVE_ TINYINT, + IS_CONCURRENT_ TINYINT, + IS_SCOPE_ TINYINT, + IS_EVENT_SCOPE_ TINYINT, + IS_MI_ROOT_ TINYINT, + SUSPENSION_STATE_ integer, + CACHED_ENT_STATE_ integer, + TENANT_ID_ varchar(255) default '', + NAME_ varchar(255), + START_TIME_ datetime(3), + START_USER_ID_ varchar(255), + LOCK_TIME_ timestamp(3) NULL, + IS_COUNT_ENABLED_ TINYINT, + EVT_SUBSCR_COUNT_ integer, + TASK_COUNT_ integer, + JOB_COUNT_ integer, + TIMER_JOB_COUNT_ integer, + SUSP_JOB_COUNT_ integer, + DEADLETTER_JOB_COUNT_ integer, + VAR_COUNT_ integer, + ID_LINK_COUNT_ integer, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_RU_JOB ( + ID_ varchar(64) NOT NULL, + REV_ integer, + TYPE_ varchar(255) NOT NULL, + LOCK_EXP_TIME_ timestamp(3) NULL, + LOCK_OWNER_ varchar(255), + EXCLUSIVE_ boolean, + EXECUTION_ID_ varchar(64), + PROCESS_INSTANCE_ID_ varchar(64), + PROC_DEF_ID_ varchar(64), + RETRIES_ integer, + EXCEPTION_STACK_ID_ varchar(64), + EXCEPTION_MSG_ varchar(4000), + DUEDATE_ timestamp(3) NULL, + REPEAT_ varchar(255), + HANDLER_TYPE_ varchar(255), + HANDLER_CFG_ varchar(4000), + TENANT_ID_ varchar(255) default '', + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_RU_TIMER_JOB ( + ID_ varchar(64) NOT NULL, + REV_ integer, + TYPE_ varchar(255) NOT NULL, + LOCK_EXP_TIME_ timestamp(3) NULL, + LOCK_OWNER_ varchar(255), + EXCLUSIVE_ boolean, + EXECUTION_ID_ varchar(64), + PROCESS_INSTANCE_ID_ varchar(64), + PROC_DEF_ID_ varchar(64), + RETRIES_ integer, + EXCEPTION_STACK_ID_ varchar(64), + EXCEPTION_MSG_ varchar(4000), + DUEDATE_ timestamp(3) NULL, + REPEAT_ varchar(255), + HANDLER_TYPE_ varchar(255), + HANDLER_CFG_ varchar(4000), + TENANT_ID_ varchar(255) default '', + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_RU_SUSPENDED_JOB ( + ID_ varchar(64) NOT NULL, + REV_ integer, + TYPE_ varchar(255) NOT NULL, + EXCLUSIVE_ boolean, + EXECUTION_ID_ varchar(64), + PROCESS_INSTANCE_ID_ varchar(64), + PROC_DEF_ID_ varchar(64), + RETRIES_ integer, + EXCEPTION_STACK_ID_ varchar(64), + EXCEPTION_MSG_ varchar(4000), + DUEDATE_ timestamp(3) NULL, + REPEAT_ varchar(255), + HANDLER_TYPE_ varchar(255), + HANDLER_CFG_ varchar(4000), + TENANT_ID_ varchar(255) default '', + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_RU_DEADLETTER_JOB ( + ID_ varchar(64) NOT NULL, + REV_ integer, + TYPE_ varchar(255) NOT NULL, + EXCLUSIVE_ boolean, + EXECUTION_ID_ varchar(64), + PROCESS_INSTANCE_ID_ varchar(64), + PROC_DEF_ID_ varchar(64), + EXCEPTION_STACK_ID_ varchar(64), + EXCEPTION_MSG_ varchar(4000), + DUEDATE_ timestamp(3) NULL, + REPEAT_ varchar(255), + HANDLER_TYPE_ varchar(255), + HANDLER_CFG_ varchar(4000), + TENANT_ID_ varchar(255) default '', + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_RE_PROCDEF ( + ID_ varchar(64) not null, + REV_ integer, + CATEGORY_ varchar(255), + NAME_ varchar(255), + KEY_ varchar(255) not null, + VERSION_ integer not null, + DEPLOYMENT_ID_ varchar(64), + RESOURCE_NAME_ varchar(4000), + DGRM_RESOURCE_NAME_ varchar(4000), + DESCRIPTION_ varchar(4000), + HAS_START_FORM_KEY_ TINYINT, + HAS_GRAPHICAL_NOTATION_ TINYINT, + SUSPENSION_STATE_ integer, + TENANT_ID_ varchar(255) default '', + ENGINE_VERSION_ varchar(255), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_RU_TASK ( + ID_ varchar(64), + REV_ integer, + EXECUTION_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + PROC_DEF_ID_ varchar(64), + NAME_ varchar(255), + PARENT_TASK_ID_ varchar(64), + DESCRIPTION_ varchar(4000), + TASK_DEF_KEY_ varchar(255), + OWNER_ varchar(255), + ASSIGNEE_ varchar(255), + DELEGATION_ varchar(64), + PRIORITY_ integer, + CREATE_TIME_ timestamp(3) NULL, + DUE_DATE_ datetime(3), + CATEGORY_ varchar(255), + SUSPENSION_STATE_ integer, + TENANT_ID_ varchar(255) default '', + FORM_KEY_ varchar(255), + CLAIM_TIME_ datetime(3), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_RU_IDENTITYLINK ( + ID_ varchar(64), + REV_ integer, + GROUP_ID_ varchar(255), + TYPE_ varchar(255), + USER_ID_ varchar(255), + TASK_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + PROC_DEF_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_RU_VARIABLE ( + ID_ varchar(64) not null, + REV_ integer, + TYPE_ varchar(255) not null, + NAME_ varchar(255) not null, + EXECUTION_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + TASK_ID_ varchar(64), + BYTEARRAY_ID_ varchar(64), + DOUBLE_ double, + LONG_ bigint, + TEXT_ varchar(4000), + TEXT2_ varchar(4000), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_RU_EVENT_SUBSCR ( + ID_ varchar(64) not null, + REV_ integer, + EVENT_TYPE_ varchar(255) not null, + EVENT_NAME_ varchar(255), + EXECUTION_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + ACTIVITY_ID_ varchar(64), + CONFIGURATION_ varchar(255), + CREATED_ timestamp(3) not null DEFAULT CURRENT_TIMESTAMP(3), + PROC_DEF_ID_ varchar(64), + TENANT_ID_ varchar(255) default '', + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_EVT_LOG ( + LOG_NR_ bigint auto_increment, + TYPE_ varchar(64), + PROC_DEF_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + EXECUTION_ID_ varchar(64), + TASK_ID_ varchar(64), + TIME_STAMP_ timestamp(3) not null, + USER_ID_ varchar(255), + DATA_ LONGBLOB, + LOCK_OWNER_ varchar(255), + LOCK_TIME_ timestamp(3) null, + IS_PROCESSED_ tinyint default 0, + primary key (LOG_NR_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_PROCDEF_INFO ( + ID_ varchar(64) not null, + PROC_DEF_ID_ varchar(64) not null, + REV_ integer, + INFO_JSON_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_ID_GROUP ( + ID_ varchar(64), + REV_ integer, + NAME_ varchar(255), + TYPE_ varchar(255), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_ID_MEMBERSHIP ( + USER_ID_ varchar(64), + GROUP_ID_ varchar(64), + primary key (USER_ID_, GROUP_ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_ID_USER ( + ID_ varchar(64), + REV_ integer, + FIRST_ varchar(255), + LAST_ varchar(255), + EMAIL_ varchar(255), + PWD_ varchar(255), + PICTURE_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_ID_INFO ( + ID_ varchar(64), + REV_ integer, + USER_ID_ varchar(64), + TYPE_ varchar(64), + KEY_ varchar(255), + VALUE_ varchar(255), + PASSWORD_ LONGBLOB, + PARENT_ID_ varchar(255), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_HI_PROCINST ( + ID_ varchar(64) not null, + PROC_INST_ID_ varchar(64) not null, + BUSINESS_KEY_ varchar(255), + PROC_DEF_ID_ varchar(64) not null, + START_TIME_ datetime(3) not null, + END_TIME_ datetime(3), + DURATION_ bigint, + START_USER_ID_ varchar(255), + START_ACT_ID_ varchar(255), + END_ACT_ID_ varchar(255), + SUPER_PROCESS_INSTANCE_ID_ varchar(64), + DELETE_REASON_ varchar(4000), + TENANT_ID_ varchar(255) default '', + NAME_ varchar(255), + primary key (ID_), + unique (PROC_INST_ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_HI_ACTINST ( + ID_ varchar(64) not null, + PROC_DEF_ID_ varchar(64) not null, + PROC_INST_ID_ varchar(64) not null, + EXECUTION_ID_ varchar(64) not null, + ACT_ID_ varchar(255) not null, + TASK_ID_ varchar(64), + CALL_PROC_INST_ID_ varchar(64), + ACT_NAME_ varchar(255), + ACT_TYPE_ varchar(255) not null, + ASSIGNEE_ varchar(255), + START_TIME_ datetime(3) not null, + END_TIME_ datetime(3), + DURATION_ bigint, + DELETE_REASON_ varchar(4000), + TENANT_ID_ varchar(255) default '', + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_HI_TASKINST ( + ID_ varchar(64) not null, + PROC_DEF_ID_ varchar(64), + TASK_DEF_KEY_ varchar(255), + PROC_INST_ID_ varchar(64), + EXECUTION_ID_ varchar(64), + NAME_ varchar(255), + PARENT_TASK_ID_ varchar(64), + DESCRIPTION_ varchar(4000), + OWNER_ varchar(255), + ASSIGNEE_ varchar(255), + START_TIME_ datetime(3) not null, + CLAIM_TIME_ datetime(3), + END_TIME_ datetime(3), + DURATION_ bigint, + DELETE_REASON_ varchar(4000), + PRIORITY_ integer, + DUE_DATE_ datetime(3), + FORM_KEY_ varchar(255), + CATEGORY_ varchar(255), + TENANT_ID_ varchar(255) default '', + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_HI_VARINST ( + ID_ varchar(64) not null, + PROC_INST_ID_ varchar(64), + EXECUTION_ID_ varchar(64), + TASK_ID_ varchar(64), + NAME_ varchar(255) not null, + VAR_TYPE_ varchar(100), + REV_ integer, + BYTEARRAY_ID_ varchar(64), + DOUBLE_ double, + LONG_ bigint, + TEXT_ varchar(4000), + TEXT2_ varchar(4000), + CREATE_TIME_ datetime(3), + LAST_UPDATED_TIME_ datetime(3), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_HI_DETAIL ( + ID_ varchar(64) not null, + TYPE_ varchar(255) not null, + PROC_INST_ID_ varchar(64), + EXECUTION_ID_ varchar(64), + TASK_ID_ varchar(64), + ACT_INST_ID_ varchar(64), + NAME_ varchar(255) not null, + VAR_TYPE_ varchar(255), + REV_ integer, + TIME_ datetime(3) not null, + BYTEARRAY_ID_ varchar(64), + DOUBLE_ double, + LONG_ bigint, + TEXT_ varchar(4000), + TEXT2_ varchar(4000), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_HI_COMMENT ( + ID_ varchar(64) not null, + TYPE_ varchar(255), + TIME_ datetime(3) not null, + USER_ID_ varchar(255), + TASK_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + ACTION_ varchar(255), + MESSAGE_ varchar(4000), + FULL_MSG_ LONGBLOB, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_HI_ATTACHMENT ( + ID_ varchar(64) not null, + REV_ integer, + USER_ID_ varchar(255), + NAME_ varchar(255), + DESCRIPTION_ varchar(4000), + TYPE_ varchar(255), + TASK_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + URL_ varchar(4000), + CONTENT_ID_ varchar(64), + TIME_ datetime(3), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table if not exists ACT_HI_IDENTITYLINK ( + ID_ varchar(64), + GROUP_ID_ varchar(255), + TYPE_ varchar(255), + USER_ID_ varchar(255), + TASK_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +-- index start -- + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_RU_EXECUTION' + and index_name = 'ACT_IDX_EXEC_BUSKEY' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_EXEC_BUSKEY on ACT_RU_EXECUTION(BUSINESS_KEY_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_RU_EXECUTION' + and index_name = 'ACT_IDC_EXEC_ROOT' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDC_EXEC_ROOT on ACT_RU_EXECUTION(ROOT_PROC_INST_ID_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_RU_TASK' + and index_name = 'ACT_IDX_TASK_CREATE' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_TASK_CREATE on ACT_RU_TASK(CREATE_TIME_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_RU_IDENTITYLINK' + and index_name = 'ACT_IDX_IDENT_LNK_USER' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_IDENT_LNK_USER on ACT_RU_IDENTITYLINK(USER_ID_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_RU_IDENTITYLINK' + and index_name = 'ACT_IDX_IDENT_LNK_GROUP' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_IDENT_LNK_GROUP on ACT_RU_IDENTITYLINK(GROUP_ID_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_RU_EVENT_SUBSCR' + and index_name = 'ACT_IDX_EVENT_SUBSCR_CONFIG_' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_EVENT_SUBSCR_CONFIG_ on ACT_RU_EVENT_SUBSCR(CONFIGURATION_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_RU_VARIABLE' + and index_name = 'ACT_IDX_VARIABLE_TASK_ID' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_VARIABLE_TASK_ID on ACT_RU_VARIABLE(TASK_ID_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_RU_IDENTITYLINK' + and index_name = 'ACT_IDX_ATHRZ_PROCEDEF' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_ATHRZ_PROCEDEF on ACT_RU_IDENTITYLINK(PROC_DEF_ID_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_PROCDEF_INFO' + and index_name = 'ACT_IDX_INFO_PROCDEF' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_INFO_PROCDEF on ACT_PROCDEF_INFO(PROC_DEF_ID_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_PROCINST' + and index_name = 'ACT_IDX_HI_PRO_INST_END' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_PRO_INST_END on ACT_HI_PROCINST(END_TIME_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_PROCINST' + and index_name = 'ACT_IDX_HI_PRO_I_BUSKEY' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_PRO_I_BUSKEY on ACT_HI_PROCINST(BUSINESS_KEY_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_ACTINST' + and index_name = 'ACT_IDX_HI_ACT_INST_START' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_ACT_INST_START on ACT_HI_ACTINST(START_TIME_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_ACTINST' + and index_name = 'ACT_IDX_HI_ACT_INST_END' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_ACT_INST_END on ACT_HI_ACTINST(END_TIME_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_DETAIL' + and index_name = 'ACT_IDX_HI_DETAIL_PROC_INST' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_DETAIL_PROC_INST on ACT_HI_DETAIL(PROC_INST_ID_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_DETAIL' + and index_name = 'ACT_IDX_HI_DETAIL_ACT_INST' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_DETAIL_ACT_INST on ACT_HI_DETAIL(ACT_INST_ID_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_DETAIL' + and index_name = 'ACT_IDX_HI_DETAIL_TIME' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_DETAIL_TIME on ACT_HI_DETAIL(TIME_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_DETAIL' + and index_name = 'ACT_IDX_HI_DETAIL_NAME' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_DETAIL_NAME on ACT_HI_DETAIL(NAME_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_DETAIL' + and index_name = 'ACT_IDX_HI_DETAIL_TASK_ID' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_DETAIL_TASK_ID on ACT_HI_DETAIL(TASK_ID_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_VARINST' + and index_name = 'ACT_IDX_HI_PROCVAR_PROC_INST' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_PROCVAR_PROC_INST on ACT_HI_VARINST(PROC_INST_ID_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_VARINST' + and index_name = 'ACT_IDX_HI_PROCVAR_NAME_TYPE' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_PROCVAR_NAME_TYPE on ACT_HI_VARINST(NAME_, VAR_TYPE_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_VARINST' + and index_name = 'ACT_IDX_HI_PROCVAR_TASK_ID' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_PROCVAR_TASK_ID on ACT_HI_VARINST(TASK_ID_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_ACTINST' + and index_name = 'ACT_IDX_HI_ACT_INST_PROCINST' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_ACT_INST_PROCINST on ACT_HI_ACTINST(PROC_INST_ID_, ACT_ID_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_ACTINST' + and index_name = 'ACT_IDX_HI_ACT_INST_EXEC' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_ACT_INST_EXEC on ACT_HI_ACTINST(EXECUTION_ID_, ACT_ID_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_IDENTITYLINK' + and index_name = 'ACT_IDX_HI_IDENT_LNK_USER' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_IDENT_LNK_USER on ACT_HI_IDENTITYLINK(USER_ID_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_IDENTITYLINK' + and index_name = 'ACT_IDX_HI_IDENT_LNK_TASK' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_IDENT_LNK_TASK on ACT_HI_IDENTITYLINK(TASK_ID_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_IDENTITYLINK' + and index_name = 'ACT_IDX_HI_IDENT_LNK_PROCINST' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_IDENT_LNK_PROCINST on ACT_HI_IDENTITYLINK(PROC_INST_ID_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = ( + select count(1) + from information_schema.statistics + where table_schema = database() + and table_name = 'ACT_HI_TASKINST' + and index_name = 'ACT_IDX_HI_TASK_INST_PROCINST' +); + +set @sql = IF(@index_exists = 0, + 'create index ACT_IDX_HI_TASK_INST_PROCINST on ACT_HI_TASKINST(PROC_INST_ID_)', + 'SELECT "Index already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @index_exists = NULL; +set @sql = NULL; + +-- index end -- + +-- constraint start -- + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_GE_BYTEARRAY' + and CONSTRAINT_NAME = 'ACT_FK_BYTEARR_DEPL' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_GE_BYTEARRAY add constraint ACT_FK_BYTEARR_DEPL foreign key (DEPLOYMENT_ID_) references ACT_RE_DEPLOYMENT (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RE_PROCDEF' + and CONSTRAINT_NAME = 'ACT_UNIQ_PROCDEF' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RE_PROCDEF add constraint ACT_UNIQ_PROCDEF unique (KEY_,VERSION_, TENANT_ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_EXECUTION' + and CONSTRAINT_NAME = 'ACT_FK_EXE_PROCINST' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_EXECUTION add constraint ACT_FK_EXE_PROCINST foreign key (PROC_INST_ID_) references ACT_RU_EXECUTION (ID_) on delete cascade on update cascade', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_EXECUTION' + and CONSTRAINT_NAME = 'ACT_FK_EXE_PARENT' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_EXECUTION add constraint ACT_FK_EXE_PARENT foreign key (PARENT_ID_) references ACT_RU_EXECUTION (ID_) on delete cascade', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_EXECUTION' + and CONSTRAINT_NAME = 'ACT_FK_EXE_SUPER' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_EXECUTION add constraint ACT_FK_EXE_SUPER foreign key (SUPER_EXEC_) references ACT_RU_EXECUTION (ID_) on delete cascade', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_EXECUTION' + and CONSTRAINT_NAME = 'ACT_FK_EXE_PROCDEF' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_EXECUTION add constraint ACT_FK_EXE_PROCDEF foreign key (PROC_DEF_ID_) references ACT_RE_PROCDEF (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_IDENTITYLINK' + and CONSTRAINT_NAME = 'ACT_FK_TSKASS_TASK' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_IDENTITYLINK add constraint ACT_FK_TSKASS_TASK foreign key (TASK_ID_) references ACT_RU_TASK (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_IDENTITYLINK' + and CONSTRAINT_NAME = 'ACT_FK_ATHRZ_PROCEDEF' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_IDENTITYLINK add constraint ACT_FK_ATHRZ_PROCEDEF foreign key (PROC_DEF_ID_) references ACT_RE_PROCDEF(ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_IDENTITYLINK' + and CONSTRAINT_NAME = 'ACT_FK_IDL_PROCINST' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_IDENTITYLINK add constraint ACT_FK_IDL_PROCINST foreign key (PROC_INST_ID_) references ACT_RU_EXECUTION (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_TASK' + and CONSTRAINT_NAME = 'ACT_FK_TASK_EXE' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_TASK add constraint ACT_FK_TASK_EXE foreign key (EXECUTION_ID_) references ACT_RU_EXECUTION (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_TASK' + and CONSTRAINT_NAME = 'ACT_FK_TASK_PROCINST' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_TASK add constraint ACT_FK_TASK_PROCINST foreign key (PROC_INST_ID_) references ACT_RU_EXECUTION (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_TASK' + and CONSTRAINT_NAME = 'ACT_FK_TASK_PROCDEF' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_TASK add constraint ACT_FK_TASK_PROCDEF foreign key (PROC_DEF_ID_) references ACT_RE_PROCDEF (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_VARIABLE' + and CONSTRAINT_NAME = 'ACT_FK_VAR_EXE' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_VARIABLE add constraint ACT_FK_VAR_EXE foreign key (EXECUTION_ID_) references ACT_RU_EXECUTION (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_VARIABLE' + and CONSTRAINT_NAME = 'ACT_FK_VAR_PROCINST' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_VARIABLE add constraint ACT_FK_VAR_PROCINST foreign key (PROC_INST_ID_) references ACT_RU_EXECUTION(ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_VARIABLE' + and CONSTRAINT_NAME = 'ACT_FK_VAR_BYTEARRAY' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_VARIABLE add constraint ACT_FK_VAR_BYTEARRAY foreign key (BYTEARRAY_ID_) references ACT_GE_BYTEARRAY (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_JOB' + and CONSTRAINT_NAME = 'ACT_FK_JOB_EXECUTION' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_JOB add constraint ACT_FK_JOB_EXECUTION foreign key (EXECUTION_ID_) references ACT_RU_EXECUTION (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_JOB' + and CONSTRAINT_NAME = 'ACT_FK_JOB_PROCESS_INSTANCE' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_JOB add constraint ACT_FK_JOB_PROCESS_INSTANCE foreign key (PROCESS_INSTANCE_ID_) references ACT_RU_EXECUTION (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_JOB' + and CONSTRAINT_NAME = 'ACT_FK_JOB_PROC_DEF' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_JOB add constraint ACT_FK_JOB_PROC_DEF foreign key (PROC_DEF_ID_) references ACT_RE_PROCDEF (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_JOB' + and CONSTRAINT_NAME = 'ACT_FK_JOB_EXCEPTION' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_JOB add constraint ACT_FK_JOB_EXCEPTION foreign key (EXCEPTION_STACK_ID_) references ACT_GE_BYTEARRAY (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_TIMER_JOB' + and CONSTRAINT_NAME = 'ACT_FK_TIMER_JOB_EXECUTION' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_TIMER_JOB add constraint ACT_FK_TIMER_JOB_EXECUTION foreign key (EXECUTION_ID_) references ACT_RU_EXECUTION (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_TIMER_JOB' + and CONSTRAINT_NAME = 'ACT_FK_TIMER_JOB_PROCESS_INSTANCE' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_TIMER_JOB add constraint ACT_FK_TIMER_JOB_PROCESS_INSTANCE foreign key (PROCESS_INSTANCE_ID_) references ACT_RU_EXECUTION (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_TIMER_JOB' + and CONSTRAINT_NAME = 'ACT_FK_TIMER_JOB_PROC_DEF' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_TIMER_JOB add constraint ACT_FK_TIMER_JOB_PROC_DEF foreign key (PROC_DEF_ID_) references ACT_RE_PROCDEF (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_TIMER_JOB' + and CONSTRAINT_NAME = 'ACT_FK_TIMER_JOB_EXCEPTION' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_TIMER_JOB add constraint ACT_FK_TIMER_JOB_EXCEPTION foreign key (EXCEPTION_STACK_ID_) references ACT_GE_BYTEARRAY (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_SUSPENDED_JOB' + and CONSTRAINT_NAME = 'ACT_FK_SUSPENDED_JOB_EXECUTION' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_SUSPENDED_JOB add constraint ACT_FK_SUSPENDED_JOB_EXECUTION foreign key (EXECUTION_ID_) references ACT_RU_EXECUTION (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_SUSPENDED_JOB' + and CONSTRAINT_NAME = 'ACT_FK_SUSPENDED_JOB_PROCESS_INSTANCE' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_SUSPENDED_JOB add constraint ACT_FK_SUSPENDED_JOB_PROCESS_INSTANCE foreign key (PROCESS_INSTANCE_ID_) references ACT_RU_EXECUTION (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_SUSPENDED_JOB' + and CONSTRAINT_NAME = 'ACT_FK_SUSPENDED_JOB_PROC_DEF' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_SUSPENDED_JOB add constraint ACT_FK_SUSPENDED_JOB_PROC_DEF foreign key (PROC_DEF_ID_) references ACT_RE_PROCDEF (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_SUSPENDED_JOB' + and CONSTRAINT_NAME = 'ACT_FK_SUSPENDED_JOB_EXCEPTION' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_SUSPENDED_JOB add constraint ACT_FK_SUSPENDED_JOB_EXCEPTION foreign key (EXCEPTION_STACK_ID_) references ACT_GE_BYTEARRAY (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_DEADLETTER_JOB' + and CONSTRAINT_NAME = 'ACT_FK_DEADLETTER_JOB_EXECUTION' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_DEADLETTER_JOB add constraint ACT_FK_DEADLETTER_JOB_EXECUTION foreign key (EXECUTION_ID_) references ACT_RU_EXECUTION (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_DEADLETTER_JOB' + and CONSTRAINT_NAME = 'ACT_FK_DEADLETTER_JOB_PROCESS_INSTANCE' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_DEADLETTER_JOB add constraint ACT_FK_DEADLETTER_JOB_PROCESS_INSTANCE foreign key (PROCESS_INSTANCE_ID_) references ACT_RU_EXECUTION (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_DEADLETTER_JOB' + and CONSTRAINT_NAME = 'ACT_FK_DEADLETTER_JOB_PROC_DEF' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_DEADLETTER_JOB add constraint ACT_FK_DEADLETTER_JOB_PROC_DEF foreign key (PROC_DEF_ID_) references ACT_RE_PROCDEF (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_DEADLETTER_JOB' + and CONSTRAINT_NAME = 'ACT_FK_DEADLETTER_JOB_EXCEPTION' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_DEADLETTER_JOB add constraint ACT_FK_DEADLETTER_JOB_EXCEPTION foreign key (EXCEPTION_STACK_ID_) references ACT_GE_BYTEARRAY (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RU_EVENT_SUBSCR' + and CONSTRAINT_NAME = 'ACT_FK_EVENT_EXEC' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RU_EVENT_SUBSCR add constraint ACT_FK_EVENT_EXEC foreign key (EXECUTION_ID_) references ACT_RU_EXECUTION(ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RE_MODEL' + and CONSTRAINT_NAME = 'ACT_FK_MODEL_SOURCE' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RE_MODEL add constraint ACT_FK_MODEL_SOURCE foreign key (EDITOR_SOURCE_VALUE_ID_) references ACT_GE_BYTEARRAY (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RE_MODEL' + and CONSTRAINT_NAME = 'ACT_FK_MODEL_SOURCE_EXTRA' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RE_MODEL add constraint ACT_FK_MODEL_SOURCE_EXTRA foreign key (EDITOR_SOURCE_EXTRA_VALUE_ID_) references ACT_GE_BYTEARRAY (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_RE_MODEL' + and CONSTRAINT_NAME = 'ACT_FK_MODEL_DEPLOYMENT' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_RE_MODEL add constraint ACT_FK_MODEL_DEPLOYMENT foreign key (DEPLOYMENT_ID_) references ACT_RE_DEPLOYMENT (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_PROCDEF_INFO' + and CONSTRAINT_NAME = 'ACT_FK_INFO_JSON_BA' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_PROCDEF_INFO add constraint ACT_FK_INFO_JSON_BA foreign key (INFO_JSON_ID_) references ACT_GE_BYTEARRAY (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_PROCDEF_INFO' + and CONSTRAINT_NAME = 'ACT_FK_INFO_PROCDEF' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_PROCDEF_INFO add constraint ACT_FK_INFO_PROCDEF foreign key (PROC_DEF_ID_) references ACT_RE_PROCDEF (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_PROCDEF_INFO' + and CONSTRAINT_NAME = 'ACT_UNIQ_INFO_PROCDEF' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_PROCDEF_INFO add constraint ACT_UNIQ_INFO_PROCDEF unique (PROC_DEF_ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_ID_MEMBERSHIP' + and CONSTRAINT_NAME = 'ACT_FK_MEMB_GROUP' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_ID_MEMBERSHIP add constraint ACT_FK_MEMB_GROUP foreign key (GROUP_ID_) references ACT_ID_GROUP (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = ( + select COUNT(*) + from information_schema.key_column_usage + where table_schema = database() + and TABLE_NAME = 'ACT_ID_MEMBERSHIP' + and CONSTRAINT_NAME = 'ACT_FK_MEMB_USER' +); +set @sql = IF(@fk_exists = 0, + 'alter table ACT_ID_MEMBERSHIP add constraint ACT_FK_MEMB_USER foreign key (USER_ID_) references ACT_ID_USER (ID_)', + 'SELECT "Constraint already exists, skipping."'); + +prepare stmt from @sql; +execute stmt; +deallocate prepare stmt; + +set @fk_exists = NULL; +set @sql = NULL; + +-- constraint end -- + +commit; \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/resources/application-local.yml b/backend/projectmanagement-server/src/main/resources/application-local.yml new file mode 100644 index 0000000..39b131e --- /dev/null +++ b/backend/projectmanagement-server/src/main/resources/application-local.yml @@ -0,0 +1,71 @@ +spring: + application: + name: projectmanagement + + datasource: + url: jdbc:mysql://localhost:3306/projectmanagement?createDatabaseIfNotExist=true + username: root + password: rootpassword123 + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + connectionTimeout: 20000 + maximumPoolSize: 20 + jpa: + database-platform: org.hibernate.dialect.MySQL8Dialect + hibernate: + ddl-auto: update # Options: none, validate, update, create, create-drop + show-sql: true + defer-datasource-initialization: false + properties: + hibernate: + format_sql: true + + + + + security: + oauth2: + resourceserver: + jwt: +# issuer-uri: http://localhost:8081/api/platform + issuer-uri: https://ikoncloud-dev.keross.com/ikon-api/platform + +ikon: + user: + agent: "IKON App Server" + time-zone: "Asia/Kolkata" + platform: + rest: + url: https://ikoncloud-dev.keross.com/ikon-api + logger: + enabled: false # set true when deployed in dev/uat/prod otherwise set to false if running in local/devtools environment. + app: + softwareId: "fe6e3390-c5e1-4797-9d59-7393d2fab5c1" + softwareVerion: 1 + softwareName: ${spring.application.name} + softwareRepositoryName: projectmanagement + accessmanagement: + init: + file: C:\Sales_CRM_New\projectmanagement\projectmanagement\bpmn\project.json + + +logging: + level: + com.ikon: DEBUG + file: + name: "../logs/${spring.application.name}-app.log" + +server: + address: 0.0.0.0 + port: 8070 + +eureka: + instance: + ip-address: ${server.address} + preferIpAddress: true + instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} + client: + register-with-eureka: false + fetch-registry: false + service-url: + default-zone: http://localhost:8761/eureka diff --git a/backend/projectmanagement-server/src/main/resources/application-server.yml b/backend/projectmanagement-server/src/main/resources/application-server.yml new file mode 100644 index 0000000..9e1b512 --- /dev/null +++ b/backend/projectmanagement-server/src/main/resources/application-server.yml @@ -0,0 +1,119 @@ +spring: + application: + name: bpmnprocessdemo + + datasource: + url: jdbc:mysql://localhost:3306/bpmnprocessdemo + username: system + password: admin + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + connectionTimeout: 20000 + maximumPoolSize: 20 + jpa: + database-platform: org.hibernate.dialect.MySQL8Dialect + hibernate: + ddl-auto: update # Options: none, validate, update, create, create-drop + show-sql: true + defer-datasource-initialization: false + properties: + hibernate: + format_sql: true + sql: + init: + schema-locations: + - classpath:activiti.engine.schema.sql + mode: always + + data: + redis: + host: localhost + port: 6379 + password: 7410 + database: 0 + lettuce: + pool: + max-active: 10 # Maximum number of connections + max-idle: 8 # Maximum idle connections + min-idle: 2 # Minimum idle connections + max-wait: 5s # Max wait time for a connection + mongodb: + host: localhost + port: 27017 + username: system + password: admin + authentication-database: admin + mongodb: + client: + settings: + connection-pool: + max-size: 500 # Maximum connections in the pool + min-size: 10 # Minimum idle connections + max-connection-idle-time: 10s # Time a connection can be idle before being closed + max-wait-time: 5s + + kafka: + bootstrap-servers: localhost:9092 + properties: + schema: + registry: + url: http://localhost:9093 + specific: + avro: + reader: true + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: io.confluent.kafka.serializers.KafkaAvroSerializer + consumer: + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: io.confluent.kafka.serializers.KafkaAvroDeserializer + group-id: ${spring.application.name}-consumer-group + auto-offset-reset: earliest + + security: + oauth2: + resourceserver: + jwt: +# issuer-uri: http://localhost:8081/api/platform + issuer-uri: https://ikoncloud-dev.keross.com/ikon-api/platform + +ikon: + user: + agent: "IKON App Server" + time-zone: "Asia/Kolkata" + platform: + rest: + url: http://localhost:8081/api + logger: + enabled: false # set true when deployed in dev/uat/prod otherwise set to false if running in local/devtools environment. + accessmanagement: + init: + file: ../../bpmn/project.json + processengine: + databaseSchemaUpdate: false # possible values: "true", "false", "create-drop" + bpmn: + enabled: true + path: "../../bpmn" + job: + max-pool-size: 100 + +logging: + level: + com.ikon: DEBUG + file: + name: "../logs/${spring.application.name}-app.log" + +server: + address: localhost + port: 8060 + +eureka: + instance: + ip-address: ${server.address} + preferIpAddress: true + instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} + client: + register-with-eureka: true + fetch-registry: true + service-url: + default-zone: http://localhost:8761/eureka diff --git a/backend/projectmanagement-server/src/main/resources/application.properties b/backend/projectmanagement-server/src/main/resources/application.properties new file mode 100644 index 0000000..1923269 --- /dev/null +++ b/backend/projectmanagement-server/src/main/resources/application.properties @@ -0,0 +1,2 @@ +# Sales-CRM URL +salescrm.api.base-url=https://ikoncloud-dev.keross.com/ikon-api diff --git a/backend/projectmanagement-server/src/main/resources/application.yml b/backend/projectmanagement-server/src/main/resources/application.yml new file mode 100644 index 0000000..4339dce --- /dev/null +++ b/backend/projectmanagement-server/src/main/resources/application.yml @@ -0,0 +1,116 @@ +spring: + main: + allow-bean-definition-overriding: true + application: + name: projectmanagement + + service: + token: + enabled: true + + # ============================= + # MySQL Database Configuration + # ============================= + datasource: + url: jdbc:mysql://192.168.3.144:3306/projectmanagement?createDatabaseIfNotExist=true + username: app_user + password: userpassword123 + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + connectionTimeout: 20000 + maximumPoolSize: 20 + + jpa: + database-platform: org.hibernate.dialect.MySQL8Dialect + hibernate: + ddl-auto: update + show-sql: true + defer-datasource-initialization: false + properties: + hibernate: + format_sql: true + + + + + # ============================= + # Security Configuration + # ============================= + security: + oauth2: + resourceserver: + jwt: + issuer-uri: https://ikoncloud-dev.keross.com/ikon-api/platform + + +# ============================= +# IKON Platform Configurations +# ============================= +ikon: + connector: + internal-auth: + tokenUri: https://ikoncloud-dev.keross.com/ikon-api/platform/oauth2/token + clientId: ikon-client-management + clientSecret: F38Ysb4VEOSXUeISKcMyvpKqxtrIL8nf + crypto: + secretKey: MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY= + + user: + agent: "IKON App Server" + platform: + rest: https://ikoncloud-dev.keross.com/ikon-api + logger: + enabled: false + accessmanagement: + init: + file: ../../bpmn/project.json + + app: + #softwareId: "bfbb1d15-7e43-46ea-9b4c-109c8574aff8" + softwareId: "ed3d5e4f-1ea9-4008-92f9-6d09cec9e0ff" + softwareVerion: 1 + softwareName: ${spring.application.name} + softwareRepositoryName: projectmanagement + + + + +# ============================= +# Sales-CRM API Configuration +# ============================= +salescrm: + api: + base-url: https://ikoncloud-dev.keross.com/ikon-api/salescrm/api/v1 + + +# ============================= +# Logging Configuration +# ============================= +logging: + level: + com.ikon: DEBUG + file: + name: "../logs/${spring.application.name}-app.log" + + +# ============================= +# Server Configuration +# ============================= +server: + address: 0.0.0.0 + port: 8071 + + +# ============================= +# Eureka (Optional) +# ============================= +eureka: + instance: + ip-address: ${server.address} + preferIpAddress: true + instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} + client: + register-with-eureka: false + fetch-registry: false + service-url: + default-zone: http://localhost:8761/eureka \ No newline at end of file diff --git a/backend/projectmanagement-server/src/main/resources/application_old.yml b/backend/projectmanagement-server/src/main/resources/application_old.yml new file mode 100644 index 0000000..d787dfc --- /dev/null +++ b/backend/projectmanagement-server/src/main/resources/application_old.yml @@ -0,0 +1,117 @@ +spring: + application: + name: projectmanagement + + datasource: + url: jdbc:mysql://localhost:3306/projectmanagement?createDatabaseIfNotExist=true + username: root + password: rootpassword123 + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + connectionTimeout: 20000 + maximumPoolSize: 20 + jpa: + database-platform: org.hibernate.dialect.MySQL8Dialect + hibernate: + ddl-auto: update # Options: none, validate, update, create, create-drop + show-sql: true + defer-datasource-initialization: false + properties: + hibernate: + format_sql: true + sql: + init: + schema-locations: + - classpath:activiti.engine.schema.sql + mode: always + + data: + redis: + host: localhost + port: 6379 + password: 7410 + database: 0 + lettuce: + pool: + max-active: 10 # Maximum number of connections + max-idle: 8 # Maximum idle connections + min-idle: 2 # Minimum idle connections + max-wait: 5s # Max wait time for a connection + mongodb: + host: localhost + port: 27017 + username: admin + password: password123 + authentication-database: admin + mongodb: + client: + settings: + connection-pool: + max-size: 500 # Maximum connections in the pool + min-size: 10 # Minimum idle connections + max-connection-idle-time: 10s # Time a connection can be idle before being closed + max-wait-time: 5s + + kafka: + bootstrap-servers: localhost:9092 + properties: + schema: + registry: + url: http://localhost:9093 + specific: + avro: + reader: true + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: io.confluent.kafka.serializers.KafkaAvroSerializer + consumer: + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: io.confluent.kafka.serializers.KafkaAvroDeserializer + group-id: ${spring.application.name}-consumer-group + auto-offset-reset: earliest + + security: + oauth2: + resourceserver: + jwt: +# issuer-uri: http://localhost:8081/api/platform + issuer-uri: https://ikoncloud-dev.keross.com/ikon-api/platform + +ikon: + user: + agent: "IKON App Server" + platform: + rest: + url: https://ikoncloud-dev.keross.com/ikon-api + logger: + enabled: false # set true when deployed in dev/uat/prod otherwise set to false if running in local/devtools environment. + accessmanagement: + init: + file: ../../bpmn/project.json + processengine: + databaseSchemaUpdate: false # possible values: "true", "false", "create-drop" + bpmn: + enabled: true + path: "../../bpmn" + +logging: + level: + com.ikon: DEBUG + file: + name: "../logs/${spring.application.name}-app.log" + +server: + address: 0.0.0.0 +# address: 127.0.0.1 + port: 8072 + +eureka: + instance: + ip-address: ${server.address} + preferIpAddress: true + instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} + client: + register-with-eureka: true + fetch-registry: true + service-url: + default-zone: http://localhost:8761/eureka diff --git a/bpmn/.gitignore b/bpmn/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/bpmn/environments.json b/bpmn/environments.json new file mode 100644 index 0000000..d51db01 --- /dev/null +++ b/bpmn/environments.json @@ -0,0 +1,8 @@ +[ + { + "envName": "local", + "applicationUrl": "http://localhost:8071", + "authUrl": "https://ikoncloud-dev.keross.com/ikon-api", + "jobUrl": "http://localhost:8083" + } +] \ No newline at end of file diff --git a/bpmn/folder_structure.json b/bpmn/folder_structure.json new file mode 100644 index 0000000..00221b0 --- /dev/null +++ b/bpmn/folder_structure.json @@ -0,0 +1,9 @@ +[ + { + "id": "ed3d5e4f-1ea9-4008-92f9-6d09cec9e0ff", + "name": "src", + "path": "C:\\Projects\\project-management\\bpmn\\src_ed3d5e4f-1ea9-4008-92f9-6d09cec9e0ff", + "nodeType": "folder", + "children": null + } +] \ No newline at end of file diff --git a/bpmn/lib/readme.md b/bpmn/lib/readme.md new file mode 100644 index 0000000..b94cf1c --- /dev/null +++ b/bpmn/lib/readme.md @@ -0,0 +1,2 @@ +# lib +This will contain additional libraries for you to use \ No newline at end of file diff --git a/bpmn/package-lock.json b/bpmn/package-lock.json new file mode 100644 index 0000000..78b62a5 --- /dev/null +++ b/bpmn/package-lock.json @@ -0,0 +1,8 @@ +{ + "name": "projectmanagement", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + } +} diff --git a/bpmn/package.json b/bpmn/package.json new file mode 100644 index 0000000..5c5bb58 --- /dev/null +++ b/bpmn/package.json @@ -0,0 +1,12 @@ +{ + "name": "projectmanagement", + "version": "1.0.0", + "main": "./src/index.js", + "type": "module", + "scripts": { + "start": "node ./src/index.js" + }, + "author": "", + "license": "ISC", + "description": "" +} diff --git a/bpmn/project.json b/bpmn/project.json new file mode 100644 index 0000000..94fec8f --- /dev/null +++ b/bpmn/project.json @@ -0,0 +1,31 @@ +{ + "projectName": "projectmanagement", + "groups": [ + { + "id": "b3a6bf0c-19f1-49b8-beac-5ccabfcf8721", + "name": "Admins", + "description": "", + "active": true + } + ], + "roles": [ + { + "id": "18980c5d-fd55-4238-896c-57006a7fe372", + "name": "Admin", + "active": true, + "description": "", + "groups": [ + "b3a6bf0c-19f1-49b8-beac-5ccabfcf8721" + ] + }, + { + "id": "0867d509-aa6c-4c3a-9abf-1b2c584c16f8", + "name": "Project manager", + "active": true, + "description": "", + "groups": [ + "b3a6bf0c-19f1-49b8-beac-5ccabfcf8721" + ] + } + ] +} \ No newline at end of file diff --git a/bpmn/src_ed3d5e4f-1ea9-4008-92f9-6d09cec9e0ff/readme.md b/bpmn/src_ed3d5e4f-1ea9-4008-92f9-6d09cec9e0ff/readme.md new file mode 100644 index 0000000..2863edd --- /dev/null +++ b/bpmn/src_ed3d5e4f-1ea9-4008-92f9-6d09cec9e0ff/readme.md @@ -0,0 +1,3 @@ +# src +src will contain all of your src folders + diff --git a/dockerfiles.yaml b/dockerfiles.yaml new file mode 100644 index 0000000..ad59daf --- /dev/null +++ b/dockerfiles.yaml @@ -0,0 +1,9 @@ +images: + - name: project-management-frontend + path: ./frontend/Dockerfile + chart: frontend + + - name: project-management-backend + path: ./backend/Dockerfile + chart: backend + context: . \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..ef2eeb7 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,59 @@ +# ---- Stage 1: Install dependencies ---- +FROM node:22-alpine AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +COPY package.json package-lock.json* ./ + +RUN npm i --force + +# ---- Stage 2: Build ---- +FROM node:22-alpine AS builder +RUN apk add --no-cache libc6-compat +WORKDIR /app + +COPY --from=deps /app/node_modules ./node_modules +COPY . . + + +# Inject build-time environment variables +ENV BASE_PATH=/apps/project-management/v1 +ENV NEXT_PUBLIC_IKON_API_URL=https://holocron.keross.com/api +ENV NEXT_PUBLIC_PROJECT_MANAGEMENT_API_URL=https://holocron.keross.com/api/project-management/v1 +ENV NEXT_PUBLIC_IKON_LOGIN_PAGE_URL=https://holocron.keross.com/ikon-portal/login.html +ENV NEXT_PUBLIC_IKON_PLATFORM_UI_URL=https://holocron.keross.com/ikon-portal + + + + +RUN npm run build + +# ---- Stage 3: Runtime ---- +FROM node:22-alpine AS runner +RUN apk add --no-cache libc6-compat +WORKDIR /app + +ENV NODE_ENV=production + +# Runtime ENV (optional override) +ENV BASE_PATH=/apps/project-management/v1 +ENV NEXT_PUBLIC_IKON_API_URL=https://holocron.keross.com/api +ENV NEXT_PUBLIC_PROJECT_MANAGEMENT_API_URL=https://holocron.keross.com/api/project-management/v1 +ENV NEXT_PUBLIC_IKON_LOGIN_PAGE_URL=https://holocron.keross.com/ikon-portal/login.html +ENV NEXT_PUBLIC_IKON_PLATFORM_UI_URL=https://holocron.keross.com/ikon-portal + + +# Create non-root user +RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs + +COPY --from=builder --chown=nextjs:nodejs /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 + +CMD ["node", "server.js"] diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..e215bc4 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/frontend/app/api/health/live/route.ts b/frontend/app/api/health/live/route.ts new file mode 100644 index 0000000..3cf5224 --- /dev/null +++ b/frontend/app/api/health/live/route.ts @@ -0,0 +1,5 @@ +import { NextResponse } from "next/server"; + +export async function GET() { + return NextResponse.json({ live: "ok" }, { status: 200 }); +} \ No newline at end of file diff --git a/frontend/app/api/health/ready/route.ts b/frontend/app/api/health/ready/route.ts new file mode 100644 index 0000000..f2a1165 --- /dev/null +++ b/frontend/app/api/health/ready/route.ts @@ -0,0 +1,5 @@ +import { NextResponse } from "next/server"; + +export async function GET() { + return NextResponse.json({ ready: "ok" }, { status: 200 }); +} \ No newline at end of file diff --git a/frontend/app/favicon.ico b/frontend/app/favicon.ico new file mode 100644 index 0000000..718d6fe Binary files /dev/null and b/frontend/app/favicon.ico differ diff --git a/frontend/app/globals.css b/frontend/app/globals.css new file mode 100644 index 0000000..5e13edb --- /dev/null +++ b/frontend/app/globals.css @@ -0,0 +1,15 @@ +/* Kendo UI Theme - local package install */ +@import "@progress/kendo-theme-default/dist/all.css"; + +@import "tailwindcss"; + +@import "ikoncomponents/dist/styles.css"; + +/* Prevent Tailwind preflight from stripping Kendo Gantt bar dimensions */ +.k-gantt .k-task-complete { + position: absolute !important; +} + +.k-gantt table { + width: 100%; +} \ No newline at end of file diff --git a/frontend/app/home/components/CTASection.tsx b/frontend/app/home/components/CTASection.tsx new file mode 100644 index 0000000..cb5935f --- /dev/null +++ b/frontend/app/home/components/CTASection.tsx @@ -0,0 +1,47 @@ +"use client"; +import { Button, Card } from "ikoncomponents"; +import { ArrowRight } from "lucide-react"; +import { useRouter } from "next/dist/client/components/navigation"; + +export function CTASection() { + const router = useRouter(); + const handleGetStarted = () => { + router.push("/main/overview/dashboard"); + }; + return ( +
+
+ + {/* Decorative radial glow */} +
+ +
+

+ Ready to Transform Your Projects? +

+

+ Join thousands of teams using AI to deliver projects on time and + under budget. +

+ +
+ + +
+
+ +
+
+ ); +} diff --git a/frontend/app/home/components/FeaturesSection.tsx b/frontend/app/home/components/FeaturesSection.tsx new file mode 100644 index 0000000..a7c3d9e --- /dev/null +++ b/frontend/app/home/components/FeaturesSection.tsx @@ -0,0 +1,210 @@ +"use client"; +import { Badge, Card } from "ikoncomponents"; +import { + Zap, + Users, + BarChart3, + FileText, + Layout, + Settings2, + Lock, + FolderKanban, + AlertTriangle, + RotateCcw, + Binary, + Link2, + Shield, + Brain, + TrendingUp, + Calendar, +} from "lucide-react"; + +const features = [ + { + title: "AI Health Scores", + desc: "Real-time project health monitoring powered by machine learning. Predict risks before they happen.", + icon: Brain, + bg: "bg-purple-600", + }, + { + title: "Predictive Analytics", + desc: "Forecast completion dates with confidence percentages. Know your slippage risk in advance.", + icon: TrendingUp, + bg: "bg-indigo-600", + }, + { + title: "AI Assessment Tool", + desc: "LLM-powered project planning wizard generating BRD, SRS, timeline, budget breakdown, and risk identification.", + icon: Zap, + bg: "bg-blue-600", + }, + { + title: "Project Management", + desc: "Complete project lifecycle management with Kanban boards, task tracking, and team collaboration.", + icon: Layout, + bg: "bg-cyan-600", + }, + { + title: "Task Management", + desc: "Create, assign, and track tasks with priorities, story points, and sprint integration.", + icon: Settings2, + bg: "bg-blue-600", + }, + { + title: "Sprint Planning", + desc: "Sprint velocity tracking, backlog grooming, and automated burndown charts for Agile teams..", + icon: Calendar, + bg: "bg-sky-600", + }, + { + title: "Analytics Dashboard", + desc: "Task distribution charts, project metrics, and financial analytics in one unified view.", + icon: BarChart3, + bg: "bg-emerald-600", + }, + { + title: "Reports & Downloads", + desc: "Generate downloadable reports filtered by time frame, projects, resources, and finances - export as CSV or PDF.", + icon: FileText, + bg: "bg-green-600", + }, + { + title: "Resource Management", + desc: "Full team member lifecycle with CRUD, CSV bulk import, onboard/offboard workflow, and FTE calculation.", + icon: Users, + bg: "bg-orange-600", + }, + { + title: "Project Access Control", + desc: "Request, grant, modify, and revoke resource access to projects with approval workflows.", + icon: Lock, + bg: "bg-amber-600", + }, + { + title: "Portfolio Management", + desc: "Department-level project grouping for strategic oversight and portfolio performance tracking.", + icon: FolderKanban, + bg: "bg-rose-600", + }, + { + title: "Risk Management", + desc: "Risk register with probability/impact scoring, severity levels, and mitigation tracking.", + icon: AlertTriangle, + bg: "bg-pink-600", + }, + { + title: "Change Management", + desc: "Variation orders with approval workflow, budget impact tracking, and schedule adjustments.", + icon: RotateCcw, + bg: "bg-pink-600", + }, + { + title: "No-Code Automation", + desc: "Create powerful If/Then triggers without writing code. Automate repetitive tasks instantly.", + icon: Binary, + bg: "bg-violet-600", + }, + { + title: "External Integrations", + desc: "Connect to HRMS, CRM, ERP systems via REST API, GraphQL, webhooks, or database probes.", + icon: Link2, + bg: "bg-indigo-600", + }, + { + title: "Enterprise Security", + desc: "Role-based access control, session management, and secure authentication for 8 stakeholder types.", + icon: Shield, + bg: "bg-orange-600", + }, +]; + +const categories = [ + { + label: "AI-Powered", + dot: "bg-fuchsia-500", + }, + { + label: "Core Features", + dot: "bg-cyan-500", + }, + { + label: "Analytics", + dot: "bg-emerald-500", + }, + { + label: "Resources", + dot: "bg-orange-500", + }, + { + label: "Management", + dot: "bg-sky-500", + }, + { + label: "Automation", + dot: "bg-violet-500", + }, + { + label: "Security", + dot: "bg-orange-500", + }, +] as const; + +export function FeaturesSection() { + return ( +
+
+ {/* Header */} +
+ + Complete Platform + + +

+ Everything You Need to Succeed +

+ +

+ A comprehensive suite of 16 powerful features designed for modern + project management. +

+
+ + {/* Categories */} +
+ {categories.map((cat) => ( + + ))} +
+ + {/* Features Grid */} +
+ {features.map((feature, idx) => ( + +
+ +
+ +

{feature.title}

+ +

+ {feature.desc} +

+
+ ))} +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/app/home/components/Footer.tsx b/frontend/app/home/components/Footer.tsx new file mode 100644 index 0000000..80bdead --- /dev/null +++ b/frontend/app/home/components/Footer.tsx @@ -0,0 +1,55 @@ +"use client"; +import { Brain} from "lucide-react"; + +const footerLinks = { + Product: ["Features", "Pricing", "Integrations", "API"], + Resources: ["Documentation", "Blog", "Support", "Community"], + Company: ["About", "Careers", "Privacy", "Terms"] +}; + +export function Footer() { + return ( +
+
+
+ + {/* Brand Column */} +
+
+
+ +
+ ProjectAI +
+

+ AI-powered project management for the modern enterprise. +

+
+ + {/* Links Columns */} + {Object.entries(footerLinks).map(([category, links]) => ( +
+

{category}

+ +
+ ))} +
+ + {/* Bottom Bar */} +
+

+ © 2025 ProjectAI. All rights reserved. +

+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/app/home/components/HeroSection.tsx b/frontend/app/home/components/HeroSection.tsx new file mode 100644 index 0000000..2eebe50 --- /dev/null +++ b/frontend/app/home/components/HeroSection.tsx @@ -0,0 +1,137 @@ +"use client"; + +import { Badge, Button, Card } from "ikoncomponents"; +import { ArrowRight, Brain, BrainCircuit } from "lucide-react"; +import { useRouter } from "next/dist/client/components/navigation"; + +export function HeroSection() { + const router = useRouter(); + + const handleStartTrial = () => { + router.push("/main/overview/dashboard"); + }; + + return ( +
+
+ {/* LEFT SECTION */} +
+ + + AI-Powered Platform + + +
+

+ Predictive Project
+ + Management + +

+ +

+ Transform reactive project tracking into proactive, predictive + management. Leverage AI and ML for executive-level insights into + financial health, resource optimization, and schedule + predictability. +

+
+ +
+ +
+ +
+
+
20%
+

+ Reduced Schedule Overruns +

+
+
+
85%
+

+ AI Prediction Accuracy +

+
+
+
+ + {/* RIGHT SECTION */} +
+ +
+
+
+
+
+
+ + + Live Preview + +
+ +
+
+

Enterprise Rollout

+

Phase 2 Active

+
+ +
+
+
92
+
+ AI Score +
+
+ +
+ +
+
+
+ +
+ + + +
+ +
+
+
+
+ 75% +
+ +
+
+
+ ); +} + +function StatBox({ + label, + value, + highlight, +}: { + label: string; + value: string; + highlight?: boolean; +}) { + return ( +
+
+ {value} +
+
{label}
+
+ ); +} diff --git a/frontend/app/home/components/IntegrationSection.tsx b/frontend/app/home/components/IntegrationSection.tsx new file mode 100644 index 0000000..942f32f --- /dev/null +++ b/frontend/app/home/components/IntegrationSection.tsx @@ -0,0 +1,61 @@ +"use client"; + +import { Badge, Card } from "ikoncomponents"; +import { Github, Slack, Code2 } from "lucide-react"; +import Image from "next/image"; + +const integrations = [ + { name: "Slack", icon: Slack, color: "text-purple-400" }, + { name: "GitHub", icon: Github, color: "text-gray-400" }, + { name: "Jira", image: "/jira.svg", color: "text-blue-400" }, + { name: "API", icon: Code2, color: "text-green-400" }, +]; + +export function IntegrationSection() { + return ( +
+
+ {/* Header */} +
+ + Ecosystem + +

+ Seamless Integrations +

+

+ Connect with your favorite tools through our RESTful API and + webhooks. +

+
+ + {/* Integration Grid */} +
+ {integrations.map((item) => ( + +
+ {item.icon ? ( + + ) : ( + {item.name} + )} +
+ + {item.name} + +
+ ))} +
+
+
+ ); +} diff --git a/frontend/app/home/components/MethodologySection.tsx b/frontend/app/home/components/MethodologySection.tsx new file mode 100644 index 0000000..28bdea5 --- /dev/null +++ b/frontend/app/home/components/MethodologySection.tsx @@ -0,0 +1,97 @@ +"use client"; + +import { Badge, Card } from "ikoncomponents"; +import { CheckCircle2, GitBranch, CalendarDays } from "lucide-react"; + +const methodologyData = [ + { + title: "Agile & Scrum", + description: + "Sprint planning, velocity tracking, and automated burndown charts.", + icon: GitBranch, + iconColor: "text-indigo-400", + iconBg: "bg-indigo-500/10", + features: [ + "Kanban Boards", + "Sprint Backlogs", + "Velocity Metrics", + "WIP Limits", + ], + }, + { + title: "Waterfall & Gantt", + description: "Dynamic Gantt charts with drag-and-drop dependency mapping.", + icon: CalendarDays, + iconColor: "text-blue-400", + iconBg: "bg-blue-500/10", + features: ["Timeline Views", "Dependencies", "Milestones", "Critical Path"], + }, +]; + +export function MethodologySection() { + return ( +
+
+ {/* Header */} +
+ + Flexible Workflows + + +

+ Your Methodology, Your Way +

+ +

+ Support diverse team workflows across Agile and Waterfall in one + unified interface. +

+
+ + {/* Cards Grid */} +
+ {methodologyData.map((item, idx) => ( + + {/* Top Section */} +
+
+ +
+ +
+

+ {item.title} +

+

+ {item.description} +

+
+
+ + {/* Features */} +
+ {item.features.map((feature) => ( +
+ + + {feature} + +
+ ))} +
+
+ ))} +
+
+
+ ); +} diff --git a/frontend/app/home/components/Navbar.tsx b/frontend/app/home/components/Navbar.tsx new file mode 100644 index 0000000..2887b5b --- /dev/null +++ b/frontend/app/home/components/Navbar.tsx @@ -0,0 +1,91 @@ +"use client"; + +import Link from "next/link"; +import { Button } from "ikoncomponents"; +import { useRouter } from "next/dist/client/components/navigation"; +import { Brain, Menu, X } from "lucide-react"; +import { useState } from "react"; + +export default function Navbar() { + const router = useRouter(); + const [open, setOpen] = useState(false); + + const handleStartTrial = () => { + router.push("/main/overview/dashboard"); + }; + + return ( +
+
+ {/* Logo */} +
+
+ +
+ ProjectAI +
+ + {/* Desktop Navigation */} + + + {/* Right Actions (Desktop) */} +
+ +
+ + {/* Mobile Menu Button */} + +
+ + {/* Mobile Navigation */} + {open && ( +
+ +
+ )} +
+ ); +} diff --git a/frontend/app/home/components/SecuritySection.tsx b/frontend/app/home/components/SecuritySection.tsx new file mode 100644 index 0000000..dba7483 --- /dev/null +++ b/frontend/app/home/components/SecuritySection.tsx @@ -0,0 +1,78 @@ +"use client"; + +import { Badge, Card } from "ikoncomponents"; +import { Lock, ShieldCheck, Globe } from "lucide-react"; + +const securityFeatures = [ + { + title: "SSO & MFA", + description: "Okta, Azure AD integration", + icon: Lock, + }, + { + title: "SOC2 & GDPR", + description: "Regulatory compliance", + icon: ShieldCheck, + }, + { + title: "Data Sovereignty", + description: "Regional data hosting", + icon: Globe, + }, +]; + +export function SecuritySection() { + return ( +
+
+ + {/* Header */} +
+ + Enterprise Grade + + +

+ Security & Compliance +

+ +

+ Built with enterprise security requirements in mind, ensuring your + data is protected at every level. +

+
+ + {/* Security Grid */} +
+ {securityFeatures.map((feature, idx) => ( + +
+ +
+ +

+ {feature.title} +

+ +

+ {feature.description} +

+
+ ))} +
+ +
+
+ ); +} diff --git a/frontend/app/home/components/VisibilitySection.tsx b/frontend/app/home/components/VisibilitySection.tsx new file mode 100644 index 0000000..ba16787 --- /dev/null +++ b/frontend/app/home/components/VisibilitySection.tsx @@ -0,0 +1,95 @@ +"use client"; + +import { Badge, Card } from "ikoncomponents"; +import { CheckCircle2 } from "lucide-react"; + +const visibilityPoints = [ + "AI Health Scores with confidence percentages", + "Real-time P/L and budget tracking", + "Resource utilization heatmaps", + "Portfolio-level performance metrics", +]; + +const dashboardStats = [ + { label: "Active Projects", value: "12" }, + { label: "Avg Health Score", value: "87%", color: "text-emerald-400" }, + { label: "Total Budget", value: "$4.2M" }, + { label: "At Risk", value: "3", color: "text-amber-500" }, +]; + +export function VisibilitySection() { + return ( +
+
+
+ + {/* Left Column */} +
+ + Executive Dashboard + + +
+

+ Complete Visibility
at a Glance +

+

+ A unified view of project health, financial metrics, and + predictive insights designed for strategic decision-making. +

+
+ +
+ {visibilityPoints.map((point) => ( +
+
+ +
+ {point} +
+ ))} +
+
+ + {/* Right Column */} +
+ {/* Background glow */} +
+ + +
+

+ Portfolio Overview +

+ + Healthy + +
+ +
+ {dashboardStats.map((stat) => ( +
+
+ {stat.value} +
+
+ {stat.label} +
+
+ ))} +
+
+
+ +
+
+
+ ); +} diff --git a/frontend/app/home/layout.tsx b/frontend/app/home/layout.tsx new file mode 100644 index 0000000..db5b922 --- /dev/null +++ b/frontend/app/home/layout.tsx @@ -0,0 +1,16 @@ +"use client"; + + +export default function HomeLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + <> + + + {children} + + ); +} diff --git a/frontend/app/home/page.tsx b/frontend/app/home/page.tsx new file mode 100644 index 0000000..b5ce7c1 --- /dev/null +++ b/frontend/app/home/page.tsx @@ -0,0 +1,22 @@ +"use client"; + +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; + +// The marketing landing page (Hero + sections) is no longer used — opening the +// app should land directly on the dashboard. We keep the same auth check as the +// root page: send unauthenticated visitors to login, everyone else to the dashboard. +export default function page() { + const router = useRouter(); + + useEffect(() => { + const hasToken = document.cookie.includes("accessToken"); + if (!hasToken) { + window.location.href = "/login.html"; + } else { + router.replace("/main/overview/dashboard"); + } + }, [router]); + + return null; +} diff --git a/frontend/app/home/projects/layout.tsx b/frontend/app/home/projects/layout.tsx new file mode 100644 index 0000000..f6e0666 --- /dev/null +++ b/frontend/app/home/projects/layout.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +export default function ProjectsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+ {children} +
+ ); +} diff --git a/frontend/app/home/projects/page.tsx b/frontend/app/home/projects/page.tsx new file mode 100644 index 0000000..fb22b80 --- /dev/null +++ b/frontend/app/home/projects/page.tsx @@ -0,0 +1,9 @@ +import { redirect } from "next/navigation"; + +export default function Project() { + return ( +
+

Project

+
+ ) +} \ No newline at end of file diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx new file mode 100644 index 0000000..3fc2315 --- /dev/null +++ b/frontend/app/layout.tsx @@ -0,0 +1,173 @@ +"use client"; +import "@progress/kendo-licensing"; +import "@progress/kendo-theme-default/dist/all.css"; +import React from "react"; + +import { ProviderWrapper } from "ikoncomponents"; + +import { Oswald, Outfit, Poppins } from "next/font/google"; +import "./globals.css"; +import { RenderSidebarNav } from "ikoncomponents/dist/ikoncomponents/main-layout/nav-main"; +import { SidebarNavItem } from "ikoncomponents/dist/ikoncomponents/main-layout/SidebarNavContext"; +import { AppCacheProvider } from "@/app/utils/context/AppCacheContext"; + +import { + Bot, + Files, + Home, + icons, + LayoutDashboard, + Settings, + User, +} from "lucide-react"; + +const poppins = Poppins({ + subsets: ["latin"], + weight: "400", + variable: "--font-poppins", +}); + +const outfit = Outfit({ + subsets: ["latin"], + weight: "400", + variable: "--font-outfit", +}); + +const oswald = Oswald({ + subsets: ["latin"], + weight: "400", + variable: "--font-oswald", +}); + +// export const metadata: Metadata = { +// title: "Create Next App", +// description: "Generated by create next app", +// }; + +const navMain: SidebarNavItem[] = [ + // { + // title: "Main", + // url: "/main", + // icon: Home, + // default: true, + // items: [ + // { + // title: "Dashboard", + // url: "/main/dashboard", + // icons: Home, + // }, + // { + // title: "Projects", + // url: "/main/projects", + // }, + + // { + // title: "Tasks", + // url: "/main/tasks", + // }, + // { + // title: "Sprints", + // url: "/main/sprints", + // }, + + + // ], + // }, + // { + // title: "Analytics", + // url: "/analytics", + // icon: Bot, + // default: true, + // items: [ + // { + // title: "Analytics", + // url: "/analytics/Analytics", + // }, + // { + // title: "Ai Insights", + // url: "/analytics/ai-insights", + // }, + + // { + // title: "Ai Assessment", + // url: "/analytics/ai-assessment", + // }, + // { + // title: "Reports", + // url: "/analytics/reports", + // }, + + // { + // title: "Resources", + // url: "/analytics/resources", + // }, + // { + // title: "Project Access", + // url: "/analytics/project-access", + // }, + // ], + // }, + + + // { + // title: "Management", + // url: "#", + // icon: Files, + // isActive: true, + // items: [ + // { + // title: "Portfolios", + // url: "/management/portfolios", + // }, + // { + // title: "Risks", + // url: "/management/risks", + // }, + + // { + // title: "Changes", + // url: "/management/changes", + // }, + // { + // title: "Automation", + // url: "/management/automation", + // }, + + // { + // title: "Integration", + // url: "/management/integration", + // }, + // { + // title: "settings", + // url: "/management/settings", + // }, + // ], + // }, + +]; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + + + + + Project AI + + } /> + {children} + + + + ); +} diff --git a/frontend/app/main/configuration/company-data/components/grade-table/index.tsx b/frontend/app/main/configuration/company-data/components/grade-table/index.tsx new file mode 100644 index 0000000..ab9cd7f --- /dev/null +++ b/frontend/app/main/configuration/company-data/components/grade-table/index.tsx @@ -0,0 +1,146 @@ +"use client"; + +import { useEffect, useMemo, useRef, useState } from "react"; +import { Search, Award } from "lucide-react"; +import { + ColumnDef, + DataTableLayout, + Card, + Input, +} from "ikoncomponents"; +import { getAllGrade } from "@/app/utils/api/companyData/gradeApi.ts"; +import { GradeResponseDto } from "@/app/utils/api/companyData/gradeApi.ts/type"; + +function GradeDataTable() { + const [gradeTableData, setGradeTableData] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [search, setSearch] = useState(""); + + // DataTableLayout defaults to list view and has no prop to start in grid, so + // once mounted we click its built-in "Grid View" toggle once to default to cards. + const tableContainerRef = useRef(null); + const defaultedToGrid = useRef(false); + + const filteredGradeTableData = useMemo(() => { + if (!search.trim()) return gradeTableData; + return gradeTableData.filter((gradeData) => + gradeData.grade?.toLowerCase().includes(search.toLowerCase()), + ); + }, [gradeTableData, search]); + + const fetchGradeTableData = async () => { + setIsLoading(true); + try { + const gradeData = await getAllGrade(); + setGradeTableData(gradeData?.content || []); + } catch (error) { + console.error("Error fetching grade data:", error); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + fetchGradeTableData(); + }, []); + + // Switch DataTableLayout into grid (card) view as soon as it mounts, once. + useEffect(() => { + if (isLoading || defaultedToGrid.current) return; + const gridButton = + tableContainerRef.current?.querySelector( + 'button[title="Grid View"]', + ); + if (gridButton) { + gridButton.click(); + defaultedToGrid.current = true; + } + }, [isLoading]); + + // ikoncomponents passes the row object directly to cell(), not { row }. + const columns: ColumnDef[] = [ + { + accessorKey: "grade", + header: "Grade", + cell: (row) => {row.grade || "n/a"}, + }, + ]; + + return ( +
+ {/* Header */} +
+
+

Grades

+

+ {filteredGradeTableData.length} of {gradeTableData.length} grades +

+
+
+ +
+ row.id, + totalPages: 1, + currentPage: 1, + isLoading, + onReload: fetchGradeTableData, + actionNode: ( +
+ +
+ setSearch(e.target.value)} + /> +
+ ), + gridComponent: (data: GradeResponseDto[]) => ( +
+ {data.length > 0 ? ( + data.map((gradeItem) => ( + + {/* Left accent rail */} + +
+ +
+
+

+ Grade +

+

+ {gradeItem.grade || "-"} +

+
+
+ )) + ) : ( +
+ No grades found. +
+ )} +
+ ), + }} + /> +
+
+ ); +} + +export default GradeDataTable; diff --git a/frontend/app/main/configuration/company-data/components/role-table/index.tsx b/frontend/app/main/configuration/company-data/components/role-table/index.tsx new file mode 100644 index 0000000..fd6b745 --- /dev/null +++ b/frontend/app/main/configuration/company-data/components/role-table/index.tsx @@ -0,0 +1,150 @@ +"use client"; + +import { useEffect, useMemo, useRef, useState } from "react"; +import { Search, Briefcase } from "lucide-react"; +import { + ColumnDef, + DataTableLayout, + Card, + Input, +} from "ikoncomponents"; +import { getAllRoles } from "@/app/utils/api/companyData/roleApi.ts"; + +interface RoleData { + id: string; + role: string; +} + +function RoleDataTable() { + const [roleTableData, setRoleTableData] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [search, setSearch] = useState(""); + + // DataTableLayout defaults to list view and has no prop to start in grid, so + // once mounted we click its built-in "Grid View" toggle once to default to cards. + const tableContainerRef = useRef(null); + const defaultedToGrid = useRef(false); + + const filteredRoleTableData = useMemo(() => { + if (!search.trim()) return roleTableData; + return roleTableData.filter((roleData) => + roleData.role?.toLowerCase().includes(search.toLowerCase()), + ); + }, [roleTableData, search]); + + const fetchRoleTableData = async () => { + setIsLoading(true); + try { + const roleData = await getAllRoles(); + setRoleTableData(roleData || []); + } catch (error) { + console.error("Error fetching role data:", error); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + fetchRoleTableData(); + }, []); + + // Switch DataTableLayout into grid (card) view as soon as it mounts, once. + useEffect(() => { + if (isLoading || defaultedToGrid.current) return; + const gridButton = + tableContainerRef.current?.querySelector( + 'button[title="Grid View"]', + ); + if (gridButton) { + gridButton.click(); + defaultedToGrid.current = true; + } + }, [isLoading]); + + // ikoncomponents passes the row object directly to cell(), not { row }. + const columns: ColumnDef[] = [ + { + accessorKey: "role", + header: "Role", + cell: (row) => {row.role || "n/a"}, + }, + ]; + + return ( +
+ {/* Header */} +
+
+

Roles

+

+ {filteredRoleTableData.length} of {roleTableData.length} roles +

+
+
+ +
+ row.id, + totalPages: 1, + currentPage: 1, + isLoading, + onReload: fetchRoleTableData, + actionNode: ( +
+ +
+ setSearch(e.target.value)} + /> +
+ ), + gridComponent: (data: RoleData[]) => ( +
+ {data.length > 0 ? ( + data.map((roleItem) => ( + + {/* Left accent rail */} + +
+ +
+
+

+ Role +

+

+ {roleItem.role || "-"} +

+
+
+ )) + ) : ( +
+ No roles found. +
+ )} +
+ ), + }} + /> +
+
+ ); +} + +export default RoleDataTable; diff --git a/frontend/app/main/configuration/company-data/components/tabs/index.tsx b/frontend/app/main/configuration/company-data/components/tabs/index.tsx new file mode 100644 index 0000000..112b42a --- /dev/null +++ b/frontend/app/main/configuration/company-data/components/tabs/index.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { CustomTabs, TabArray } from "ikoncomponents"; +import GradeDataTable from "../grade-table"; +import RoleDataTable from "../role-table"; + +const tabArray: TabArray[] = [ + { + tabName: "Role", + tabId: "tab-role", + default: true, + tabContent: , + }, + { + tabName: "Grade", + tabId: "tab-grade", + default: false, + tabContent: , + }, +]; + +export default function CompanyDataTab() { + return ( +
+ +
+ ); +} diff --git a/frontend/app/main/configuration/company-data/layout.tsx b/frontend/app/main/configuration/company-data/layout.tsx new file mode 100644 index 0000000..deb2f88 --- /dev/null +++ b/frontend/app/main/configuration/company-data/layout.tsx @@ -0,0 +1,15 @@ +"use client"; +import { RenderAppBreadcrumb } from "ikoncomponents"; + +export default function CompanyDataLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>){ + return ( + <> + + {children} + + ); +} \ No newline at end of file diff --git a/frontend/app/main/configuration/company-data/page.tsx b/frontend/app/main/configuration/company-data/page.tsx new file mode 100644 index 0000000..7dab6cc --- /dev/null +++ b/frontend/app/main/configuration/company-data/page.tsx @@ -0,0 +1,9 @@ +import CompanyDataTab from "./components/tabs"; + +export default async function CompanyData() { + return ( +
+ +
+ ); +} diff --git a/frontend/app/main/configuration/employee-data/components/employee-table/index.tsx b/frontend/app/main/configuration/employee-data/components/employee-table/index.tsx new file mode 100644 index 0000000..e2b70b6 --- /dev/null +++ b/frontend/app/main/configuration/employee-data/components/employee-table/index.tsx @@ -0,0 +1,172 @@ +"use client"; + +import { useState, useEffect, useCallback, useRef } from "react"; +import { ColumnDef, DataTableLayout } from "ikoncomponents"; +import { useSearchParams } from "next/navigation"; + +import { getEmployees } from "@/app/utils/api/employeeDetails"; + +interface EmployeeResponseDto { + accountId: string; + empId: string; + name: string; + email: string; + organizationEmail: string; + role: string; + grade: string; + active: boolean; +} + +interface RoleDto { + id: string; + accountId: string; + role: string; +} + +interface GradeDto { + id: string; + grade: string; +} + +interface ResolvedEmployee extends EmployeeResponseDto { + roleName: string; + gradeName: string; +} + +function EmployeePage({ + roleData, + gradeData, +}: { + roleData: RoleDto[]; + gradeData: GradeDto[]; +}) { + const [employees, setEmployees] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + // This table is list-only, so hide DataTableLayout's built-in List/Grid toggle + // (it has no prop to disable it). Once mounted we find the grid button and hide + // the whole toggle group, so a lone list button isn't left dangling. + const tableContainerRef = useRef(null); + const hidGridToggle = useRef(false); + + const searchParams = useSearchParams(); + const currentSearch = (searchParams.get("search") || "").toLowerCase().trim(); + + const fetchEmployees = useCallback(async () => { + setIsLoading(true); + + try { + const data = await getEmployees(); + console.log("emp data", data); + + const empList: EmployeeResponseDto[] = data || []; + + const roleMap = new Map( + roleData?.map((role) => [role.id, role.role]) || [], + ); + + const gradeMap = new Map( + gradeData?.map((grade) => [grade.id, grade.grade]) || [], + ); + console.log("emp", empList, roleMap, gradeMap); + + const resolved: ResolvedEmployee[] = empList.map((emp) => ({ + ...emp, + roleName: roleMap.get(emp.role) || "n/a", + gradeName: gradeMap.get(emp.grade) || "n/a", + })); + console.log("emp data", resolved); + setEmployees(resolved); + } catch (error) { + console.error("Failed to fetch employees:", error); + } finally { + setIsLoading(false); + } + }, [roleData, gradeData]); + + useEffect(() => { + if (roleData && gradeData) { + fetchEmployees(); + } + }, [fetchEmployees, roleData, gradeData]); + + // Hide the List/Grid toggle once the table has mounted. + useEffect(() => { + if (isLoading || hidGridToggle.current) return; + const gridButton = + tableContainerRef.current?.querySelector( + 'button[title="Grid View"]', + ); + if (gridButton?.parentElement) { + gridButton.parentElement.style.display = "none"; + hidGridToggle.current = true; + } + }, [isLoading]); + + const filteredEmployees = currentSearch + ? employees.filter( + (emp) => + (emp.name || "").toLowerCase().includes(currentSearch) || + (emp.empId || "").toLowerCase().includes(currentSearch) || + (emp.email || "").toLowerCase().includes(currentSearch) || + (emp.roleName || "").toLowerCase().includes(currentSearch) || + (emp.gradeName || "").toLowerCase().includes(currentSearch), + ) + : employees; + + const columns: ColumnDef[] = [ + { + accessorKey: "empId", + header: "Employee ID", + cell: (row) => {row.empId || "n/a"}, + }, + { + accessorKey: "name", + header: "Employee Name", + cell: (row) => {row.name || "n/a"}, + }, + { + accessorKey: "roleName", + header: "Role", + cell: (row) => {row.roleName || "n/a"}, + }, + { + accessorKey: "gradeName", + header: "Grade", + cell: (row) => {row.gradeName || "n/a"}, + }, + { + accessorKey: "email", + header: "Email", + cell: (row) => {row.email || "n/a"}, + }, + { + accessorKey: "organizationEmail", + header: "Organization Email", + cell: (row) => {row.organizationEmail || "n/a"}, + }, + { + accessorKey: "active", + header: "Status", + cell: (row) => {row.active ? "Active" : "Inactive"}, + }, + ]; + + return ( +
+ row.empId, + totalPages: 1, + currentPage: 1, + isLoading, + onReload: fetchEmployees, + }} + /> +
+ ); +} + +export default EmployeePage; diff --git a/frontend/app/main/configuration/employee-data/components/employeeCard/index.tsx b/frontend/app/main/configuration/employee-data/components/employeeCard/index.tsx new file mode 100644 index 0000000..896711f --- /dev/null +++ b/frontend/app/main/configuration/employee-data/components/employeeCard/index.tsx @@ -0,0 +1,103 @@ +"use client"; + +import { + Card, + CardContent, + CardHeader, + Badge, + Avatar, + AvatarFallback, + Separator, +} from "ikoncomponents"; + +interface EmployeeResponseDto { + accountId: string; + empId: string; + name: string; + email: string; + organizationEmail: string; + role: string; + grade: string; + active: boolean; +} + +const EmployeeCard = ({ + empId, + name, + email, + organizationEmail, + role, + grade, + active, +}: EmployeeResponseDto) => { + const initials = name + ?.split(" ") + .map((n: string) => n[0]) + .join("") + .toUpperCase() + .slice(0, 2); + + return ( + + +
+
+ + + {initials} + + +
+

{name}

+

{empId}

+
+
+ +
+ + {active ? "Active" : "Inactive"} + +
+
+
+ + + + +
+
+

+ Role +

+

{role || "n/a"}

+
+
+

+ Grade +

+

{grade || "n/a"}

+
+
+ + + +
+
+

+ Personal Email +

+

{email || "n/a"}

+
+
+

+ Work Email +

+

{organizationEmail || "n/a"}

+
+
+
+
+ ); +}; + +export default EmployeeCard; \ No newline at end of file diff --git a/frontend/app/main/configuration/employee-data/layout.tsx b/frontend/app/main/configuration/employee-data/layout.tsx new file mode 100644 index 0000000..9118db3 --- /dev/null +++ b/frontend/app/main/configuration/employee-data/layout.tsx @@ -0,0 +1,21 @@ +"use client"; +import { RenderAppBreadcrumb } from "ikoncomponents"; + +export default function EmployeeDataLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + <> + + {children} + + ); +} diff --git a/frontend/app/main/configuration/employee-data/page.tsx b/frontend/app/main/configuration/employee-data/page.tsx new file mode 100644 index 0000000..32f68c0 --- /dev/null +++ b/frontend/app/main/configuration/employee-data/page.tsx @@ -0,0 +1,19 @@ +import { getAllGrade } from "@/app/utils/api/companyData/gradeApi.ts"; +import EmployeeDataTable from "./components/employee-table"; +import { getAllRoles } from "@/app/utils/api/companyData/roleApi.ts"; + +export const dynamic = "force-dynamic"; + +export default async function EmployeeData() { + const gradeData = await getAllGrade(); + const roleData = await getAllRoles(); + + return ( +
+ +
+ ); +} diff --git a/frontend/app/main/configuration/fx-rate/components/fx-rate-table/index.tsx b/frontend/app/main/configuration/fx-rate/components/fx-rate-table/index.tsx new file mode 100644 index 0000000..dfa3047 --- /dev/null +++ b/frontend/app/main/configuration/fx-rate/components/fx-rate-table/index.tsx @@ -0,0 +1,98 @@ +"use client"; + +import { useEffect, useMemo, useRef } from "react"; +import { ColumnDef, DataTableLayout } from "ikoncomponents"; +import { useAppCache } from "@/app/utils/context/AppCacheContext"; + +export interface FXRateData { + id: string; + year: string; + currency: string; + fxRate: number; + activeStatus: boolean; +} + +function FXRateDataTable() { + // FX rate data comes from the shared app-wide cache. + const { fxRateResponse, isLoading, refresh } = useAppCache(); + + const fxRateTableData = useMemo(() => { + const rows: FXRateData[] = []; + (fxRateResponse?.content || []).forEach((item: any) => { + const fxRateDetails = item.fxRateDetails || {}; + Object.values(fxRateDetails).forEach((yearGroup: any) => { + Object.values(yearGroup).forEach((detail: any) => { + rows.push({ + id: detail.id, + year: detail.year, + currency: detail.currency, + fxRate: detail.fxRate, + activeStatus: detail.activeStatus, + }); + }); + }); + }); + return rows; + }, [fxRateResponse]); + + // This table is list-only, so hide DataTableLayout's built-in List/Grid toggle + // (it has no prop to disable it). Once mounted we find the grid button and hide + // the whole toggle group, so a lone list button isn't left dangling. + const tableContainerRef = useRef(null); + const hidGridToggle = useRef(false); + + useEffect(() => { + if (isLoading || hidGridToggle.current) return; + const gridButton = + tableContainerRef.current?.querySelector( + 'button[title="Grid View"]', + ); + if (gridButton?.parentElement) { + gridButton.parentElement.style.display = "none"; + hidGridToggle.current = true; + } + }, [isLoading]); + + const columns: ColumnDef[] = [ + { + accessorKey: "year", + header: "Year", + cell: (row) => {row.year ?? "n/a"}, + }, + { + accessorKey: "currency", + header: "Currency", + cell: (row) => {row.currency ?? "n/a"}, + }, + { + accessorKey: "fxRate", + header: "FX Rate", + cell: (row) => {row.fxRate ?? "n/a"}, + }, + { + accessorKey: "activeStatus", + header: "Status", + cell: (row) => ( + {row.activeStatus ? "Active" : "Inactive"} + ), + }, + ]; + + return ( +
+ row.id, + totalPages: 1, + currentPage: 1, + isLoading, + onReload: refresh, + }} + /> +
+ ); +} + +export default FXRateDataTable; diff --git a/frontend/app/main/configuration/fx-rate/layout.tsx b/frontend/app/main/configuration/fx-rate/layout.tsx new file mode 100644 index 0000000..9e21ae3 --- /dev/null +++ b/frontend/app/main/configuration/fx-rate/layout.tsx @@ -0,0 +1,15 @@ +"use client"; +import { RenderAppBreadcrumb } from "ikoncomponents"; + +export default function FXRateLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>){ + return ( + <> + + {children} + + ); +} \ No newline at end of file diff --git a/frontend/app/main/configuration/fx-rate/page.tsx b/frontend/app/main/configuration/fx-rate/page.tsx new file mode 100644 index 0000000..54c5f2b --- /dev/null +++ b/frontend/app/main/configuration/fx-rate/page.tsx @@ -0,0 +1,9 @@ +import FXRateDataTable from "./components/fx-rate-table"; + +export default async function CompanyData() { + return ( +
+ +
+ ); +} diff --git a/frontend/app/main/configuration/integration/components/AddIntegrationModal.tsx b/frontend/app/main/configuration/integration/components/AddIntegrationModal.tsx new file mode 100644 index 0000000..0cb2008 --- /dev/null +++ b/frontend/app/main/configuration/integration/components/AddIntegrationModal.tsx @@ -0,0 +1,238 @@ +"use client"; + +import { X } from "lucide-react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; + + +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + Button, + Form, + FormField, + FormItem, + FormLabel, + FormControl, + FormMessage, + Input, + Textarea, + Select, + SelectTrigger, + SelectValue, + SelectContent, + SelectItem, +} from "ikoncomponents"; + +/* ---------------- Schema ---------------- */ +const integrationSchema = z.object({ + name: z.string().min(1, "Integration name is required"), + systemType: z.string().min(1, "System type is required"), + connectionMethod: z.string().min(1, "Connection method is required"), + endpointUrl: z.string().url("Invalid URL").optional().or(z.literal("")), + description: z.string().optional(), +}); + +type IntegrationFormValues = z.infer; + +interface Props { + open: boolean; + onClose: () => void; + onSubmit: (data: IntegrationFormValues) => void; +} + +/* ---------------- Component ---------------- */ +export default function AddIntegrationModal({ + open, + onClose, + onSubmit, +}: Props) { + const form = useForm({ + resolver: zodResolver(integrationSchema), + defaultValues: { + name: "", + systemType: "", + connectionMethod: "", + endpointUrl: "", + description: "", + }, + }); + + const handleClose = () => { + form.reset(); + onClose(); + }; + + const handleCreateIntegration = (data: IntegrationFormValues) => { + console.log("Integration form data:", { + ...data, + }); + + onSubmit(data); // pass to parent if needed + handleClose(); // close + reset modal +}; + + + return ( + + + {/* Header */} + + Add External Integration + + + + + + {/* Form */} +
+ + {/* Integration Name */} + ( + + + Integration Name * + + + + + + + )} + /> + + {/* System Type */} + ( + + + System Type * + + + + + )} + /> + + {/* Connection Method */} + ( + + + Connection Method{" "} + * + + + + + )} + /> + + {/* Endpoint URL */} + ( + + Endpoint URL + + + + + + )} + /> + + {/* Description */} + ( + + Description + +