Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f5edaaa
create tables for team objectives
devni-a Jul 17, 2025
5070271
Get team objectives endpoint implementation wip
devni-a Jul 28, 2025
4d63a4e
Create and use mapper for response
devni-a Jul 28, 2025
aed6ca5
Fix the mapper to correctly map teamObjective.id to teamObjectiveId
devni-a Jul 29, 2025
da3bfaa
Add test
devni-a Jul 29, 2025
0ed192e
Formatting
devni-a Jul 29, 2025
d351c17
Formatting
devni-a Jul 29, 2025
9bdad67
feat: create tables for team objectives
devni-a Jul 17, 2025
f55fd60
feat: Get team objectives endpoint implementation wip
devni-a Jul 28, 2025
80ec5f8
feat: Create and use mapper for response
devni-a Jul 28, 2025
2c56ce9
feat: Fix the mapper to correctly map teamObjective.id to teamObjecti…
devni-a Jul 29, 2025
2ae3dc5
feat: Add test
devni-a Jul 29, 2025
600d335
feat: Formatting
devni-a Jul 29, 2025
3cee28d
feat: Formatting
devni-a Jul 29, 2025
d3f376d
feat: Add endpoint to get team objective by ID
devni-a Jul 29, 2025
1dc3028
feat: Refactor and add test
devni-a Jul 29, 2025
d129b60
Merge remote-tracking branch 'devni-a/feat/643-as-a-user-I-must-be-ab…
brp-imalsha Jul 29, 2025
7c0def7
Resolve conflicts
Dlnmdu Jul 29, 2025
6e1d7dd
feat: Create team objectives endpoint implementation
brp-imalsha Aug 6, 2025
a8b71f6
resolve conflicts
Dlnmdu Aug 7, 2025
e5a541a
Change
Dlnmdu Aug 7, 2025
5916dfb
feat: test commit
milindaRootcode Aug 12, 2025
32bd77f
Merge branch 'main' into feat/643-as-a-user-I-must-be-able-to-view-th…
devni-a Aug 25, 2025
06e14b0
feat: Refactor code for team objectives
Attigala Aug 25, 2025
a4788ea
Add team objectives
Dlnmdu Aug 25, 2025
add51a8
Resolve conflicts
Dlnmdu Aug 26, 2025
b17c401
Add missing comma
Dlnmdu Aug 26, 2025
92ee866
Merge pull request #4 from Dlnmdu/drawer-menu-item
Attigala Aug 27, 2025
e1dc472
Merge branch 'main' into feat/643-as-a-user-I-must-be-able-to-view-th…
Attigala Oct 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ public enum OkrMessageConstant implements MessageConstant {

// Error messages
OKR_ERROR_COMPANY_OBJECTIVE_NOT_FOUND("api.error.okr.company-objective-not-found"),
OKR_ERROR_TIME_PERIOD_DOES_NOT_MATCH_FREQUENCY("api.error.okr.okr-time-not-match-frequency");
OKR_ERROR_TIME_PERIOD_DOES_NOT_MATCH_FREQUENCY("api.error.okr.okr-time-not-match-frequency"),
TEAM_OBJECTIVE_ERROR_OBJECTIVE_NOT_FOUND("api.error.okr.team.objective.not.found"),
TEAM_OBJECTIVE_ERROR_DUPLICATE_TEAM_ID("api.error.okr.team.duplicate-team-id"),
TEAM_OBJECTIVE_ERROR_INVALID_ASSIGNED_TEAM_FOR_KEY_RESULT(
"api.error.okr.team.invalid-assigned-team-for-key-result"),
TEAM_OBJECTIVE_ERROR_INVALID_KEY_RESULT_LIMITS("api.error.okr.team.invalid-key-result-limits"),
TEAM_OBJECTIVE_ERROR_INVALID_KEY_RESULT_TYPE("api.error.okr.team.invalid-key-result-type"),
TEAM_OBJECTIVE_ERROR_NO_TEAMS_ASSIGNED("api.error.okr.team.no-teams-assigned");

private final String messageKey;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.skapp.community.okrplanner.controller.v1;

import com.skapp.community.common.payload.response.ResponseEntityDto;
import com.skapp.community.okrplanner.service.KeyResultTypeService;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("v1/key-result-types")
public class KeyResultTypeController {

private final KeyResultTypeService keyResultTypeService;

@Operation(summary = "Get Key Result Types", description = "Retrieve the default key result types.")
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN', 'ROLE_OKR_ADMIN')")
@GetMapping
public ResponseEntity<ResponseEntityDto> getKeyResultTypes() {

ResponseEntityDto responseEntityDto = keyResultTypeService.getKeyResultTypes();

return ResponseEntity.ok(responseEntityDto);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.skapp.community.okrplanner.controller.v1;

import com.skapp.community.okrplanner.payload.request.TeamObjectiveRequestDto;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import com.skapp.community.common.payload.response.ResponseEntityDto;
import com.skapp.community.okrplanner.service.TeamObjectiveService;

import org.springframework.http.ResponseEntity;

@RestController
@RequiredArgsConstructor
@RequestMapping("v1/team-objectives")
public class TeamObjectivesController {

private final TeamObjectiveService teamObjectiveService;

@Operation(summary = "Get Team objectives",
description = "Retrieve team objectives based on team ID and effective time period.")
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN', 'ROLE_OKR_ADMIN')")
@GetMapping
public ResponseEntity<ResponseEntityDto> getTeamObjectives(@RequestParam Long teamId,
@RequestParam Long effectiveTimePeriod) {

ResponseEntityDto response = teamObjectiveService.findTeamObjectivesByTeamAndEffectiveTimePeriod(teamId,
effectiveTimePeriod);
return new ResponseEntity<>(response, HttpStatus.OK);
}

@Operation(summary = "Get Team objective by ID", description = "Retrieve team objective details using ID.")
@PreAuthorize("hasAnyAuthority('ROLE_SUPER_ADMIN', 'ROLE_OKR_ADMIN')")
@GetMapping("/{id}")
public ResponseEntity<ResponseEntityDto> getTeamObjectiveById(@PathVariable Long id) {
ResponseEntityDto response = teamObjectiveService.findTeamObjectiveById(id);

return new ResponseEntity<>(response, HttpStatus.OK);
}

@Operation(summary = "Create Team Objective",
description = "Create a new team objective with the provided details.")
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN', 'ROLE_OKR_ADMIN')")
@PostMapping
public ResponseEntity<ResponseEntityDto> createTeamObjective(
@RequestBody @Valid TeamObjectiveRequestDto requestDto) {
ResponseEntityDto response = teamObjectiveService.createTeamObjective(requestDto);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.skapp.community.okrplanner.mapper;

import com.skapp.community.okrplanner.model.KeyResultAssignedTeam;
import com.skapp.community.okrplanner.model.KeyResults;
import com.skapp.community.okrplanner.model.TeamObjective;
import com.skapp.community.okrplanner.model.TeamObjectiveAssignedTeam;
import com.skapp.community.okrplanner.payload.response.AssignedTeamResponseDto;
import com.skapp.community.okrplanner.payload.response.KeyResultResponseDto;
import com.skapp.community.okrplanner.payload.response.TeamObjectiveDetailedResponseDto;
import com.skapp.community.okrplanner.payload.request.TeamObjectiveRequestDto;
import com.skapp.community.okrplanner.payload.response.TeamObjectiveResponseDto;
import com.skapp.community.okrplanner.payload.request.KeyResultRequestDto;

import com.skapp.community.okrplanner.type.KeyResultType;
import com.skapp.community.peopleplanner.model.Team;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

import java.util.List;
import java.util.stream.Collectors;

@Mapper(componentModel = "spring")
public interface TeamObjectiveMapper {

@Mapping(target = "teamObjectiveId", source = "id")
TeamObjectiveResponseDto teamObjectiveToTeamObjectiveResponseDto(TeamObjective teamObjective);

List<TeamObjectiveResponseDto> teamObjectivesToTeamObjectiveResponseDto(List<TeamObjective> teamObjective);

@Mapping(target = "teamId", source = "team.teamId")
@Mapping(target = "name", source = "team.teamName")
AssignedTeamResponseDto teamObjectiveAssignedTeamToAssignedTeamResponseDto(
TeamObjectiveAssignedTeam teamObjectiveAssignedTeam);

@Mapping(target = "teamId", source = "team.teamId")
@Mapping(target = "name", source = "team.teamName")
AssignedTeamResponseDto keyResultAssignedTeamToAssignedTeamResponseDto(KeyResultAssignedTeam keyResultAssignedTeam);

@Mapping(target = "id", source = "keyResultId")
KeyResultResponseDto keyResultsToKeyResultResponseDto(KeyResults keyResult);

@Mapping(target = "teamObjectiveId", source = "id")
TeamObjectiveDetailedResponseDto teamObjectiveToTeamObjectiveDetailedResponseDto(TeamObjective teamObjective);

default TeamObjective toEntity(TeamObjectiveRequestDto dto) {
if (dto == null)
return null;
TeamObjective entity = new TeamObjective();
entity.setTitle(dto.getTitle());
entity.setEffectiveTimePeriod(dto.getEffectiveTimePeriod());
entity.setDuration(dto.getDuration());
// TODO: Map companyObjectiveId once company objectives feature is implemented.

if (dto.getAssignedTeamIds() != null) {
List<TeamObjectiveAssignedTeam> assignedTeams = dto.getAssignedTeamIds().stream().map(teamId -> {
TeamObjectiveAssignedTeam tat = new TeamObjectiveAssignedTeam();
Team team = new Team();
team.setTeamId(teamId);
tat.setTeam(team);
tat.setTeamObjective(entity);
return tat;
}).collect(Collectors.toList());
entity.setAssignedTeams(assignedTeams);
}
if (dto.getKeyResults() != null) {
List<KeyResults> keyResults = dto.getKeyResults()
.stream()
.map(krDto -> mapKeyResult(krDto, entity))
.collect(Collectors.toList());
entity.setKeyResults(keyResults);
}
return entity;
}

default KeyResults mapKeyResult(KeyResultRequestDto krDto, TeamObjective parent) {
KeyResults kr = new KeyResults();
kr.setTitle(krDto.getTitle());
kr.setType(KeyResultType.valueOf(krDto.getType()));
kr.setLowerLimit(krDto.getLowerLimit());
kr.setUpperLimit(krDto.getUpperLimit());
kr.setTeamObjective(parent);
if (krDto.getAssignedTeamIds() != null) {
List<KeyResultAssignedTeam> assignedTeams = krDto.getAssignedTeamIds().stream().map(teamId -> {
KeyResultAssignedTeam krat = new KeyResultAssignedTeam();
Team team = new Team();
team.setTeamId(teamId);
krat.setTeam(team);
krat.setKeyResults(kr);
return krat;
}).collect(Collectors.toList());
kr.setAssignedTeams(assignedTeams);
}
return kr;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.skapp.community.okrplanner.model;

import com.skapp.community.peopleplanner.model.Team;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "key_result_assigned_team")
public class KeyResultAssignedTeam {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, updatable = false)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "key_result_id", nullable = false)
private KeyResults keyResults;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id", referencedColumnName = "team_id")
private Team team;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.skapp.community.okrplanner.model;

import com.skapp.community.okrplanner.type.KeyResultType;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.List;

@Entity
@Table(name = "key_result")
@Getter
@Setter
@NoArgsConstructor
public class KeyResults {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long keyResultId;

@Column(name = "title")
private String title;

@Enumerated(EnumType.STRING)
@Column(name = "type")
private KeyResultType type;

@Column(name = "lower_limit")
private Double lowerLimit;

@Column(name = "upper_limit")
private Double upperLimit;

@OneToMany(mappedBy = "keyResults", cascade = CascadeType.ALL, orphanRemoval = true)
private List<KeyResultAssignedTeam> assignedTeams;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_objective_id")
private TeamObjective teamObjective;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.skapp.community.okrplanner.model;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.List;

@Entity
@Table(name = "team_objective")
@Getter
@Setter
public class TeamObjective {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;

@Column(name = "title")
private String title;

@Column(name = "effective_time_period")
private Long effectiveTimePeriod;

@Column(name = "duration")
private String duration;

@OneToMany(mappedBy = "teamObjective", cascade = CascadeType.ALL, orphanRemoval = true)
private List<TeamObjectiveAssignedTeam> assignedTeams;

@OneToMany(mappedBy = "teamObjective", cascade = CascadeType.ALL, orphanRemoval = true)
private List<KeyResults> keyResults;

// TODO: OneTOOne relationship with CompanyObjective when it is created

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.skapp.community.okrplanner.model;

import com.skapp.community.peopleplanner.model.Team;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "team_objective_assigned_team")
public class TeamObjectiveAssignedTeam {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, updatable = false)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_objective_id")
private TeamObjective teamObjective;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id", referencedColumnName = "team_id")
private Team team;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.skapp.community.okrplanner.payload.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;

@Data
public class KeyResultRequestDto {

@NotBlank(message = "Key result title is required")
@Size(max = 250, message = "Title cannot exceed 250 characters")
private String title;

@NotBlank(message = "Key result type is required")
private String type;

private Double lowerLimit;

private Double upperLimit;

@NotEmpty(message = "Assigned teams are required")
private List<Long> assignedTeamIds;

}
Loading