diff --git a/backend/src/main/java/com/skapp/community/crmplanner/controller/v2/CrmBoardControllerV2.java b/backend/src/main/java/com/skapp/community/crmplanner/controller/v2/CrmBoardControllerV2.java deleted file mode 100644 index ee9d3e726e..0000000000 --- a/backend/src/main/java/com/skapp/community/crmplanner/controller/v2/CrmBoardControllerV2.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.skapp.community.crmplanner.controller.v2; - -import com.skapp.community.common.payload.response.ResponseEntityDto; -import com.skapp.community.crmplanner.service.v2.CrmDealServiceV2; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -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("/v2/crm/board") -@Tag(name = "CRM Board Controller V2", description = "Operations related to CRM board") -public class CrmBoardControllerV2 { - - private final CrmDealServiceV2 crmDealServiceV2; - - @Operation(summary = "Get board init data", - description = "Returns the shared CRM lookup set: stages, contacts, CRM roles, owners and task types. " - + "Related records are carried as id references only - a contact names its company by id rather " - + "than nesting it.") - @GetMapping("/init-data") - @PreAuthorize("hasAnyRole('ROLE_CRM_SALES_REPRESENTATIVE')") - public ResponseEntity getBoardInitData() { - ResponseEntityDto response = crmDealServiceV2.getBoardInitData(); - return new ResponseEntity<>(response, HttpStatus.OK); - } - -} diff --git a/backend/src/main/java/com/skapp/community/crmplanner/controller/v2/CrmDealControllerV2.java b/backend/src/main/java/com/skapp/community/crmplanner/controller/v2/CrmDealControllerV2.java deleted file mode 100644 index 332862aa82..0000000000 --- a/backend/src/main/java/com/skapp/community/crmplanner/controller/v2/CrmDealControllerV2.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.skapp.community.crmplanner.controller.v2; - -import com.skapp.community.common.payload.response.ResponseEntityDto; -import com.skapp.community.crmplanner.payload.request.CrmDealCreateRequestDto; -import com.skapp.community.crmplanner.payload.request.CrmDealEditRequestDto; -import com.skapp.community.crmplanner.payload.request.CrmDealFilterDto; -import com.skapp.community.crmplanner.service.v2.CrmDealServiceV2; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -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.PatchMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/v2/crm/deal") -@Tag(name = "CRM Deal Controller V2", description = "Operations related to CRM deals") -public class CrmDealControllerV2 { - - private final CrmDealServiceV2 crmDealService; - - @Operation(summary = "Create a new deal", - description = "This endpoint creates a new CRM deal with the provided details.") - @PostMapping - @PreAuthorize("hasAnyRole('ROLE_CRM_SALES_REPRESENTATIVE')") - public ResponseEntity createDeal(@RequestBody CrmDealCreateRequestDto crmDealCreateRequestDto) { - ResponseEntityDto response = crmDealService.createDeal(crmDealCreateRequestDto); - return new ResponseEntity<>(response, HttpStatus.CREATED); - } - - @Operation(summary = "Get deals", description = "Returns a paginated list of CRM deals with optional filtering.") - @GetMapping - @PreAuthorize("hasAnyRole('ROLE_CRM_SALES_REPRESENTATIVE')") - public ResponseEntity getDeals(CrmDealFilterDto crmDealFilterDto) { - ResponseEntityDto response = crmDealService.getDeals(crmDealFilterDto); - return new ResponseEntity<>(response, HttpStatus.OK); - } - - @Operation(summary = "Edit an existing deal by ID", description = "Updates details of an existing deal.") - @PatchMapping("/{id}") - @PreAuthorize("hasAnyRole('ROLE_CRM_SALES_REPRESENTATIVE')") - public ResponseEntity editDeal(@PathVariable Long id, - @RequestBody CrmDealEditRequestDto requestDto) { - ResponseEntityDto response = crmDealService.editDeal(id, requestDto); - return new ResponseEntity<>(response, HttpStatus.OK); - } - - @Operation(summary = "Get deal by ID", description = "Returns a single CRM deal by its ID.") - @GetMapping("/{id}") - @PreAuthorize("hasAnyRole('ROLE_CRM_SALES_REPRESENTATIVE')") - public ResponseEntity getDealById(@PathVariable Long id) { - ResponseEntityDto response = crmDealService.getDealById(id); - return new ResponseEntity<>(response, HttpStatus.OK); - } - -} diff --git a/backend/src/main/java/com/skapp/community/crmplanner/payload/response/CrmDealBoardInitDataResponseDto.java b/backend/src/main/java/com/skapp/community/crmplanner/payload/response/CrmDealBoardInitDataResponseDto.java deleted file mode 100644 index bdde897229..0000000000 --- a/backend/src/main/java/com/skapp/community/crmplanner/payload/response/CrmDealBoardInitDataResponseDto.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.skapp.community.crmplanner.payload.response; - -import lombok.Getter; -import lombok.Setter; - -import java.util.List; - -@Getter -@Setter -public class CrmDealBoardInitDataResponseDto { - - private List stages; - - private List contacts; - - private List crmRoles; - - private List owners; - -} diff --git a/backend/src/main/java/com/skapp/community/crmplanner/service/v2/CrmDealServiceV2.java b/backend/src/main/java/com/skapp/community/crmplanner/service/v2/CrmDealServiceV2.java deleted file mode 100644 index c31fa840fd..0000000000 --- a/backend/src/main/java/com/skapp/community/crmplanner/service/v2/CrmDealServiceV2.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.skapp.community.crmplanner.service.v2; - -import com.skapp.community.common.payload.response.ResponseEntityDto; -import com.skapp.community.crmplanner.payload.request.CrmDealCreateRequestDto; -import com.skapp.community.crmplanner.payload.request.CrmDealEditRequestDto; -import com.skapp.community.crmplanner.payload.request.CrmDealFilterDto; - -public interface CrmDealServiceV2 { - - ResponseEntityDto getBoardInitData(); - - ResponseEntityDto getDeals(CrmDealFilterDto filterDto); - - ResponseEntityDto getDealById(Long id); - - ResponseEntityDto createDeal(CrmDealCreateRequestDto requestDto); - - ResponseEntityDto editDeal(Long id, CrmDealEditRequestDto requestDto); - -} diff --git a/backend/src/main/java/com/skapp/community/crmplanner/service/v2/impl/CrmDealServiceImplV2.java b/backend/src/main/java/com/skapp/community/crmplanner/service/v2/impl/CrmDealServiceImplV2.java deleted file mode 100644 index ef46f72b77..0000000000 --- a/backend/src/main/java/com/skapp/community/crmplanner/service/v2/impl/CrmDealServiceImplV2.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.skapp.community.crmplanner.service.v2.impl; - -import com.skapp.community.common.exception.ModuleException; -import com.skapp.community.common.model.User; -import com.skapp.community.common.payload.response.PageDto; -import com.skapp.community.common.payload.response.ResponseEntityDto; -import com.skapp.community.common.service.UserService; -import com.skapp.community.crmplanner.constant.CrmMessageConstant; -import com.skapp.community.crmplanner.mapper.CrmMapper; -import com.skapp.community.crmplanner.mapper.CrmMapperV2; -import com.skapp.community.crmplanner.model.CrmDeal; -import com.skapp.community.crmplanner.model.CrmDealStage; -import com.skapp.community.crmplanner.payload.request.CrmDealCreateRequestDto; -import com.skapp.community.crmplanner.payload.request.CrmDealEditRequestDto; -import com.skapp.community.crmplanner.payload.request.CrmDealFilterDto; -import com.skapp.community.crmplanner.payload.response.CrmTaskTypeResponseDto; -import com.skapp.community.crmplanner.payload.response.board.CrmBoardOwnerResponseDto; -import com.skapp.community.crmplanner.payload.response.board.CrmBoardStageResponseDto; -import com.skapp.community.crmplanner.payload.response.v2.CrmBoardContactResponseDtoV2; -import com.skapp.community.crmplanner.payload.response.v2.CrmBoardInitDataResponseDtoV2; -import com.skapp.community.crmplanner.payload.response.v2.CrmDealResponseDtoV2; -import com.skapp.community.crmplanner.repository.CrmContactDao; -import com.skapp.community.crmplanner.repository.CrmContactOwnerRepository; -import com.skapp.community.crmplanner.repository.CrmDealDao; -import com.skapp.community.crmplanner.repository.CrmDealStageDao; -import com.skapp.community.crmplanner.repository.CrmTaskTypeDao; -import com.skapp.community.crmplanner.service.CrmDealService; -import com.skapp.community.crmplanner.service.v2.CrmDealServiceV2; -import com.skapp.community.crmplanner.util.CrmUtil; -import com.skapp.community.crmplanner.util.CrmValidations; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -@Service -@Slf4j -@RequiredArgsConstructor -public class CrmDealServiceImplV2 implements CrmDealServiceV2 { - - private final CrmDealService crmDealService; - - private final CrmDealDao crmDealDao; - - private final CrmDealStageDao crmDealStageDao; - - private final CrmContactDao crmContactDao; - - private final CrmContactOwnerRepository crmContactOwnerRepository; - - private final CrmTaskTypeDao crmTaskTypeDao; - - private final CrmMapper crmMapper; - - private final CrmMapperV2 crmMapperV2; - - private final UserService userService; - - @Override - @Transactional(readOnly = true) - public ResponseEntityDto getBoardInitData() { - log.info("getBoardInitData: execution started"); - - List visibleStages = filterVisibleDealStages( - crmDealStageDao.findAllByIsDeletedFalseOrderByOrderIndexAsc()); - List stages = crmMapper.crmDealStagesToCrmBoardStageResponseDtos(visibleStages); - - List contacts = crmContactDao.findAllContactsForBoardInitV2(); - - List owners = crmContactOwnerRepository.findAllOwnersV2(); - - List taskTypes = crmMapper - .crmTaskTypesToCrmTaskTypeResponseDtos(crmTaskTypeDao.findAllByOrderByOrderIndexAscIdAsc()); - - CrmBoardInitDataResponseDtoV2 responseDto = new CrmBoardInitDataResponseDtoV2(); - responseDto.setStages(stages); - responseDto.setContacts(contacts); - responseDto.setOwners(owners); - responseDto.setTaskTypes(taskTypes); - - log.info("getBoardInitData: execution ended"); - return new ResponseEntityDto(false, responseDto); - } - - protected List filterVisibleDealStages(List stages) { - return stages; - } - - @Override - @Transactional(readOnly = true) - public ResponseEntityDto getDeals(CrmDealFilterDto filterDto) { - log.info("getDeals: execution started"); - - User currentUser = userService.getCurrentUser(); - Long ownerId = CrmUtil.isCrmSalesRepresentative(currentUser) ? currentUser.getEmployee().getEmployeeId() : null; - - Page dealsPage = crmDealDao.findDealsV2(filterDto, ownerId, - PageRequest.of(filterDto.getPage(), filterDto.getSize())); - - PageDto pageDto = new PageDto(); - pageDto.setItems(dealsPage.getContent()); - pageDto.setCurrentPage(dealsPage.getNumber()); - pageDto.setTotalItems(dealsPage.getTotalElements()); - pageDto.setTotalPages(dealsPage.getTotalPages()); - - log.info("getDeals: execution ended with {} result(s)", dealsPage.getNumberOfElements()); - return new ResponseEntityDto(false, pageDto); - } - - @Override - @Transactional(readOnly = true) - public ResponseEntityDto getDealById(Long id) { - log.info("getDealById: execution started"); - - CrmDeal deal = crmDealDao.findByIdWithAssociations(id); - if (deal == null) { - throw new ModuleException(CrmMessageConstant.CRM_ERROR_DEAL_NOT_FOUND); - } - - User currentUser = userService.getCurrentUser(); - if (CrmValidations.isOwnerRestrictedForRepresentative(currentUser, deal.getOwner().getEmployeeId())) { - throw new ModuleException(CrmMessageConstant.CRM_ERROR_DEAL_VIEW_DENIED); - } - - log.info("getDealById: execution ended"); - return new ResponseEntityDto(false, CrmUtil.toDealResponseDtoV2(crmMapperV2, deal)); - } - - @Override - @Transactional - public ResponseEntityDto createDeal(CrmDealCreateRequestDto requestDto) { - log.info("createDeal: execution started"); - - CrmDeal savedDeal = crmDealService.persistNewDeal(requestDto); - - log.info("createDeal: execution ended"); - return new ResponseEntityDto(false, CrmUtil.toDealResponseDtoV2(crmMapperV2, savedDeal)); - } - - @Override - @Transactional - public ResponseEntityDto editDeal(Long id, CrmDealEditRequestDto requestDto) { - log.info("editDeal: execution started"); - - CrmDeal savedDeal = crmDealService.applyDealEdit(id, requestDto); - - log.info("editDeal: execution ended"); - return new ResponseEntityDto(false, CrmUtil.toDealResponseDtoV2(crmMapperV2, savedDeal)); - } - -} diff --git a/backend/src/test/java/com/skapp/community/crmplanner/controller/v2/CrmBoardControllerV2IntegrationTest.java b/backend/src/test/java/com/skapp/community/crmplanner/controller/v2/CrmBoardControllerV2IntegrationTest.java deleted file mode 100644 index 5bd44859f8..0000000000 --- a/backend/src/test/java/com/skapp/community/crmplanner/controller/v2/CrmBoardControllerV2IntegrationTest.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.skapp.community.crmplanner.controller.v2; - -import com.skapp.TestSkappApplication; -import com.skapp.community.common.service.JwtService; -import com.skapp.community.common.type.Role; -import com.skapp.community.crmplanner.model.CrmCompany; -import com.skapp.community.crmplanner.model.CrmContact; -import com.skapp.community.crmplanner.model.CrmTaskType; -import com.skapp.community.crmplanner.repository.CrmCompanyDao; -import com.skapp.community.crmplanner.repository.CrmContactDao; -import com.skapp.community.crmplanner.repository.CrmTaskTypeDao; -import com.skapp.community.peopleplanner.repository.EmployeeDao; -import com.skapp.community.peopleplanner.repository.EmployeeRoleDao; -import com.skapp.support.SecurityTestUtils; -import lombok.RequiredArgsConstructor; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; -import org.springframework.http.MediaType; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.transaction.annotation.Transactional; - -import static com.skapp.support.TestConstants.RESULTS_0_PATH; -import static com.skapp.support.TestConstants.STATUS_PATH; -import static com.skapp.support.TestConstants.STATUS_SUCCESSFUL; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest(classes = TestSkappApplication.class) -@AutoConfigureMockMvc -@Transactional -@RequiredArgsConstructor -@DisplayName("CRM Board Controller V2 Integration Tests") -class CrmBoardControllerV2IntegrationTest { - - private static final String BASE_PATH = "/v2/crm/board/init-data"; - - private final MockMvc mvc; - - private final JwtService jwtService; - - private final UserDetailsService userDetailsService; - - private final CrmCompanyDao crmCompanyDao; - - private final CrmContactDao crmContactDao; - - private final CrmTaskTypeDao crmTaskTypeDao; - - private final EmployeeDao employeeDao; - - private final EmployeeRoleDao employeeRoleDao; - - private String repToken; - - private CrmCompany company; - - private CrmContact contact; - - @BeforeEach - void setup() { - employeeDao.findById(2L).orElseThrow().getEmployeeRole().setCrmRole(Role.CRM_SALES_REPRESENTATIVE); - employeeRoleDao.flush(); - repToken = jwtService.generateAccessToken(userDetailsService.loadUserByUsername("user2@gmail.com"), 1L); - - company = new CrmCompany(); - company.setName("Board V2 Company"); - crmCompanyDao.save(company); - - contact = new CrmContact(); - contact.setName("Board V2 Contact"); - contact.setEmail("board.v2.contact@example.com"); - contact.setCompany(company); - contact.setOwner(employeeDao.getReferenceById(1L)); - crmContactDao.save(contact); - - CrmTaskType taskType = new CrmTaskType(); - taskType.setName("Call"); - taskType.setOrderIndex(1); - crmTaskTypeDao.save(taskType); - } - - @Test - @DisplayName("Board init data - Carries a contact's company as an id, not a nested object") - void getBoardInitData_ContactCompanyIsAnIdReference() throws Exception { - mvc.perform(get(BASE_PATH).accept(MediaType.APPLICATION_JSON).with(SecurityTestUtils.bearerToken(repToken))) - .andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath(STATUS_PATH).value(STATUS_SUCCESSFUL)) - .andExpect(jsonPath(RESULTS_0_PATH + "['contacts'][?(@.id == " + contact.getId() + ")].name") - .value("Board V2 Contact")) - .andExpect(jsonPath(RESULTS_0_PATH + "['contacts'][?(@.id == " + contact.getId() + ")].companyId") - .value(company.getId().intValue())) - .andExpect(jsonPath(RESULTS_0_PATH + "['contacts'][?(@.id == " + contact.getId() + ")].company") - .doesNotExist()); - } - - @Test - @DisplayName("Board init data - Returns the shared lookup set alongside the contacts") - void getBoardInitData_ReturnsSharedLookupSet() throws Exception { - mvc.perform(get(BASE_PATH).accept(MediaType.APPLICATION_JSON).with(SecurityTestUtils.bearerToken(repToken))) - .andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath(STATUS_PATH).value(STATUS_SUCCESSFUL)) - .andExpect(jsonPath(RESULTS_0_PATH + "['stages']").isArray()) - .andExpect(jsonPath(RESULTS_0_PATH + "['contacts']").isNotEmpty()) - .andExpect(jsonPath(RESULTS_0_PATH + "['owners']").isArray()) - .andExpect(jsonPath(RESULTS_0_PATH + "['taskTypes']").isNotEmpty()); - } - - @Test - @DisplayName("Board init data without CRM role - Returns Forbidden") - void getBoardInitData_WithoutCrmRole_ReturnsForbidden() throws Exception { - String noCrmRoleToken = jwtService.generateAccessToken(userDetailsService.loadUserByUsername("user4@gmail.com"), - 1L); - - mvc.perform( - get(BASE_PATH).accept(MediaType.APPLICATION_JSON).with(SecurityTestUtils.bearerToken(noCrmRoleToken))) - .andDo(print()) - .andExpect(status().isForbidden()); - } - -} diff --git a/backend/src/test/java/com/skapp/community/crmplanner/controller/v2/CrmDealControllerV2IntegrationTest.java b/backend/src/test/java/com/skapp/community/crmplanner/controller/v2/CrmDealControllerV2IntegrationTest.java deleted file mode 100644 index 6102313db5..0000000000 --- a/backend/src/test/java/com/skapp/community/crmplanner/controller/v2/CrmDealControllerV2IntegrationTest.java +++ /dev/null @@ -1,373 +0,0 @@ -package com.skapp.community.crmplanner.controller.v2; - -import com.skapp.TestSkappApplication; -import com.skapp.community.common.service.JwtService; -import com.skapp.community.common.type.Role; -import com.skapp.community.crmplanner.model.CrmCompany; -import com.skapp.community.crmplanner.model.CrmContact; -import com.skapp.community.crmplanner.model.CrmDeal; -import com.skapp.community.crmplanner.model.CrmDealStage; -import com.skapp.community.crmplanner.payload.request.CrmDealCreateRequestDto; -import com.skapp.community.crmplanner.payload.request.CrmDealEditRequestDto; -import com.skapp.community.crmplanner.repository.CrmCompanyDao; -import com.skapp.community.crmplanner.repository.CrmContactDao; -import com.skapp.community.crmplanner.repository.CrmDealDao; -import com.skapp.community.crmplanner.repository.CrmDealStageDao; -import com.skapp.community.crmplanner.type.CrmDealPriority; -import com.skapp.community.crmplanner.type.CrmDealStageType; -import com.skapp.community.crmplanner.type.CrmIndustry; -import com.skapp.community.peopleplanner.repository.EmployeeDao; -import com.skapp.community.peopleplanner.repository.EmployeeRoleDao; -import com.skapp.support.SecurityTestUtils; -import lombok.RequiredArgsConstructor; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; -import org.springframework.http.MediaType; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.ResultActions; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.transaction.annotation.Transactional; -import tools.jackson.databind.json.JsonMapper; - -import static com.skapp.support.TestConstants.RESULTS_0_PATH; -import static com.skapp.support.TestConstants.STATUS_PATH; -import static com.skapp.support.TestConstants.STATUS_SUCCESSFUL; -import static com.skapp.support.TestConstants.STATUS_UNSUCCESSFUL; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest(classes = TestSkappApplication.class) -@AutoConfigureMockMvc -@Transactional -@RequiredArgsConstructor -@DisplayName("CRM Deal Controller V2 Integration Tests") -class CrmDealControllerV2IntegrationTest { - - private static final String BASE_PATH = "/v2/crm/deal"; - - /** - * Mounted on the v1 controller so the v2 clients reach it through their /v1 base URL. - */ - private static final String BATCH_PATH = "/v1/crm/deal/batch"; - - private final MockMvc mvc; - - private final JwtService jwtService; - - private final UserDetailsService userDetailsService; - - private final JsonMapper objectMapper; - - private final CrmCompanyDao crmCompanyDao; - - private final CrmContactDao crmContactDao; - - private final CrmDealStageDao crmDealStageDao; - - private final CrmDealDao crmDealDao; - - private final EmployeeDao employeeDao; - - private final EmployeeRoleDao employeeRoleDao; - - private String authToken; - - @BeforeEach - void setup() { - authToken = jwtService.generateAccessToken(userDetailsService.loadUserByUsername("user1@gmail.com"), 1L); - } - - private ResultActions performRequest(MockHttpServletRequestBuilder request) throws Exception { - return mvc.perform(request.with(SecurityTestUtils.bearerToken(authToken))); - } - - private ResultActions performPostRequest(CrmDealCreateRequestDto dto) throws Exception { - return performRequest(post(BASE_PATH).contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(dto)) - .accept(MediaType.APPLICATION_JSON)); - } - - private ResultActions performGetByIdRequest(Long id) throws Exception { - return performRequest(get(BASE_PATH + "/" + id).accept(MediaType.APPLICATION_JSON)); - } - - private ResultActions performPatchRequest(Long id, CrmDealEditRequestDto dto) throws Exception { - return performRequest(patch(BASE_PATH + "/" + id).contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(dto)) - .accept(MediaType.APPLICATION_JSON)); - } - - private ResultActions performGetDealsRequest(Long companyId) throws Exception { - return performRequest( - get(BASE_PATH).param("companyId", companyId.toString()).accept(MediaType.APPLICATION_JSON)); - } - - private CrmDealStage savedStage() { - CrmDealStage stage = new CrmDealStage(); - stage.setName("V2 Stage"); - stage.setColor("#AABBCC"); - stage.setOrderIndex(1); - stage.setStageType(CrmDealStageType.OPEN); - return crmDealStageDao.save(stage); - } - - private CrmCompany savedCompany(String name) { - CrmCompany company = new CrmCompany(); - company.setName(name); - company.setIndustry(CrmIndustry.TECHNOLOGY_INFORMATION_AND_MEDIA); - company.setWebsite("https://deal-v2.com"); - company.setAddress("7 Deal Rd"); - company.setContactNumber("94771111111"); - return crmCompanyDao.save(company); - } - - private CrmContact savedContact(CrmCompany company, String email) { - CrmContact contact = new CrmContact(); - contact.setName("Deal Test Contact"); - contact.setEmail(email); - contact.setCompany(company); - contact.setOwner(employeeDao.getReferenceById(1L)); - return crmContactDao.save(contact); - } - - private CrmDealCreateRequestDto validPayload(Long stageId, Long contactId) { - CrmDealCreateRequestDto dto = new CrmDealCreateRequestDto(); - dto.setName("Test Deal V2"); - dto.setDescription("Deal description V2"); - dto.setAmount("5000"); - dto.setPriority(CrmDealPriority.MEDIUM); - dto.setStageId(stageId); - dto.setContactId(contactId); - dto.setOwnerId(1L); - return dto; - } - - private CrmDeal savedDeal(String name, CrmDealStage stage, CrmCompany company, CrmContact contact, Long ownerId) { - CrmDeal deal = new CrmDeal(); - deal.setName(name); - deal.setStage(stage); - deal.setCompany(company); - deal.setContact(contact); - deal.setOwner(employeeDao.getReferenceById(ownerId)); - deal.setPriority(CrmDealPriority.MEDIUM); - deal.setAmount("5000"); - deal.setDescription("Saved deal V2"); - deal.setOrderIndex("a0"); - return crmDealDao.save(deal); - } - - // --- createDeal --- - - @Test - @DisplayName("Create deal - Returns Created with scalar stage, owner, company and contact ids") - void createDeal_HappyPath_ReturnsScalarAssociationIds() throws Exception { - CrmDealStage stage = savedStage(); - CrmCompany company = savedCompany("Deal V2 Corp"); - CrmContact contact = savedContact(company, "deal.create.v2@example.com"); - - performPostRequest(validPayload(stage.getId(), contact.getId())).andDo(print()) - .andExpect(status().isCreated()) - .andExpect(jsonPath(STATUS_PATH).value(STATUS_SUCCESSFUL)) - .andExpect(jsonPath(RESULTS_0_PATH + "['name']").value("Test Deal V2")) - .andExpect(jsonPath(RESULTS_0_PATH + "['amount']").value("5000")) - .andExpect(jsonPath(RESULTS_0_PATH + "['priority']").value("MEDIUM")) - .andExpect(jsonPath(RESULTS_0_PATH + "['stageId']").value(stage.getId())) - .andExpect(jsonPath(RESULTS_0_PATH + "['ownerId']").value(1)) - .andExpect(jsonPath(RESULTS_0_PATH + "['companyId']").value(company.getId())) - .andExpect(jsonPath(RESULTS_0_PATH + "['contactId']").value(contact.getId())); - } - - // --- getDealById --- - - @Test - @DisplayName("Get deal by ID - Returns scalar stage, owner, company and contact ids") - void getDealById_HappyPath_ReturnsScalarAssociationIds() throws Exception { - CrmDealStage stage = savedStage(); - CrmCompany company = savedCompany("Deal Detail V2 Corp"); - CrmContact contact = savedContact(company, "deal.detail.v2@example.com"); - CrmDeal deal = savedDeal("Saved Deal V2", stage, company, contact, 1L); - - performGetByIdRequest(deal.getId()).andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath(STATUS_PATH).value(STATUS_SUCCESSFUL)) - .andExpect(jsonPath(RESULTS_0_PATH + "['id']").value(deal.getId())) - .andExpect(jsonPath(RESULTS_0_PATH + "['name']").value("Saved Deal V2")) - .andExpect(jsonPath(RESULTS_0_PATH + "['stageId']").value(stage.getId())) - .andExpect(jsonPath(RESULTS_0_PATH + "['ownerId']").value(1)) - .andExpect(jsonPath(RESULTS_0_PATH + "['companyId']").value(company.getId())) - .andExpect(jsonPath(RESULTS_0_PATH + "['contactId']").value(contact.getId())); - } - - @Test - @DisplayName("Get deals filtered by companyId - Returns paginated deals with scalar association ids") - void getDeals_FilterByCompanyId_ReturnsMatchingDeals() throws Exception { - CrmDealStage stage = savedStage(); - CrmCompany company = savedCompany("Deal List V2 Corp"); - CrmContact contact = savedContact(company, "deal.list.v2@example.com"); - savedDeal("List Deal V2", stage, company, contact, 1L); - - performGetDealsRequest(company.getId()).andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath(STATUS_PATH).value(STATUS_SUCCESSFUL)) - .andExpect(jsonPath(RESULTS_0_PATH + "['items'].length()").value(1)) - .andExpect(jsonPath(RESULTS_0_PATH + "['items'][0]['name']").value("List Deal V2")) - .andExpect(jsonPath(RESULTS_0_PATH + "['items'][0]['companyId']").value(company.getId())) - .andExpect(jsonPath(RESULTS_0_PATH + "['items'][0]['contactId']").value(contact.getId())); - } - - @Test - @DisplayName("Get deal by ID as Sales Representative viewing another owner's deal - Returns view-denied error") - void getDealById_SalesRepViewingOthersDeal_ReturnsBadRequest() throws Exception { - employeeDao.findById(2L).orElseThrow().getEmployeeRole().setCrmRole(Role.CRM_SALES_REPRESENTATIVE); - employeeRoleDao.flush(); - - CrmDealStage stage = savedStage(); - CrmCompany company = savedCompany("Rep Restricted V2 Corp"); - CrmContact contact = savedContact(company, "deal.rep.v2@example.com"); - CrmDeal deal = savedDeal("Admin Owned Deal V2", stage, company, contact, 1L); - - authToken = jwtService.generateAccessToken(userDetailsService.loadUserByUsername("user2@gmail.com"), 1L); - - performGetByIdRequest(deal.getId()).andDo(print()) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath(STATUS_PATH).value(STATUS_UNSUCCESSFUL)); - } - - @Test - @DisplayName("Get deals - Masks soft-deleted company on deal") - void getDeals_SoftDeletedCompany_MasksCompany() throws Exception { - CrmDealStage stage = savedStage(); - CrmCompany company = savedCompany("List Deleted Co V2"); - CrmContact contact = savedContact(company, "deal.list.deleted.v2@example.com"); - savedDeal("List Deleted Co Deal V2", stage, company, contact, 1L); - - company.setIsDeleted(true); - crmCompanyDao.save(company); - - performGetDealsRequest(company.getId()).andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath(RESULTS_0_PATH + "['items'].length()").value(1)) - .andExpect(jsonPath(RESULTS_0_PATH + "['items'][0]['name']").value("List Deleted Co Deal V2")) - .andExpect(jsonPath(RESULTS_0_PATH + "['items'][0]['companyId']").doesNotExist()); - } - - @Test - @DisplayName("Get deal by ID with soft-deleted company - Masks company on deal") - void getDealById_SoftDeletedCompany_MasksCompany() throws Exception { - CrmDealStage stage = savedStage(); - CrmCompany company = savedCompany("Deleted Co V2"); - CrmContact contact = savedContact(company, "deal.deletedco.v2@example.com"); - CrmDeal deal = savedDeal("Deleted Co Deal V2", stage, company, contact, 1L); - - company.setIsDeleted(true); - crmCompanyDao.save(company); - - performGetByIdRequest(deal.getId()).andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath(STATUS_PATH).value(STATUS_SUCCESSFUL)) - .andExpect(jsonPath(RESULTS_0_PATH + "['id']").value(deal.getId())) - .andExpect(jsonPath(RESULTS_0_PATH + "['companyId']").doesNotExist()); - } - - @Test - @DisplayName("Get deal by ID that does not exist - Returns Bad Request") - void getDealById_NotFound_ReturnsBadRequest() throws Exception { - performGetByIdRequest(999999L).andDo(print()) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath(STATUS_PATH).value(STATUS_UNSUCCESSFUL)); - } - - // --- editDeal --- - - @Test - @DisplayName("Edit deal - Returns OK with updated deal and scalar association ids") - void editDeal_HappyPath_ReturnsUpdatedDeal() throws Exception { - CrmDealStage stage = savedStage(); - CrmCompany company = savedCompany("Deal Edit V2 Corp"); - CrmContact contact = savedContact(company, "deal.edit.v2@example.com"); - CrmDeal deal = savedDeal("Original Deal V2", stage, company, contact, 1L); - - CrmDealEditRequestDto editDto = new CrmDealEditRequestDto(); - editDto.setName("Updated Deal V2"); - - performPatchRequest(deal.getId(), editDto).andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath(STATUS_PATH).value(STATUS_SUCCESSFUL)) - .andExpect(jsonPath(RESULTS_0_PATH + "['name']").value("Updated Deal V2")) - .andExpect(jsonPath(RESULTS_0_PATH + "['stageId']").value(stage.getId())) - .andExpect(jsonPath(RESULTS_0_PATH + "['ownerId']").value(1)) - .andExpect(jsonPath(RESULTS_0_PATH + "['companyId']").value(company.getId())) - .andExpect(jsonPath(RESULTS_0_PATH + "['contactId']").value(contact.getId())); - } - - @Test - @DisplayName("Edit deal as Sales Representative editing another owner's deal - Returns edit-denied error") - void editDeal_SalesRepEditingOthersDeal_ReturnsBadRequest() throws Exception { - employeeDao.findById(2L).orElseThrow().getEmployeeRole().setCrmRole(Role.CRM_SALES_REPRESENTATIVE); - employeeRoleDao.flush(); - - CrmDealStage stage = savedStage(); - CrmCompany company = savedCompany("Rep Edit Restricted V2 Corp"); - CrmContact contact = savedContact(company, "deal.rep.edit.v2@example.com"); - CrmDeal deal = savedDeal("Admin Owned Deal V2", stage, company, contact, 1L); - - authToken = jwtService.generateAccessToken(userDetailsService.loadUserByUsername("user2@gmail.com"), 1L); - - CrmDealEditRequestDto editDto = new CrmDealEditRequestDto(); - editDto.setName("Hijacked Deal V2"); - - performPatchRequest(deal.getId(), editDto).andDo(print()) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath(STATUS_PATH).value(STATUS_UNSUCCESSFUL)); - } - - @Test - @DisplayName("Create deal without CRM role - Returns Forbidden") - void createDeal_WithoutCrmRole_ReturnsForbidden() throws Exception { - CrmDealStage stage = savedStage(); - CrmCompany company = savedCompany("Forbidden Deal V2 Corp"); - CrmContact contact = savedContact(company, "deal.forbidden.v2@example.com"); - - authToken = jwtService.generateAccessToken(userDetailsService.loadUserByUsername("user2@gmail.com"), 1L); - - performPostRequest(validPayload(stage.getId(), contact.getId())).andDo(print()) - .andExpect(status().isForbidden()); - } - - private CrmDeal savedDealWithPriority(String name, CrmDealStage stage, CrmCompany company, CrmContact contact, - CrmDealPriority priority) { - CrmDeal deal = savedDeal(name, stage, company, contact, 1L); - deal.setPriority(priority); - return crmDealDao.save(deal); - } - - @Test - @DisplayName("Get deals sorted by priority - Returns severity order, not alphabetical order") - void getDeals_SortByPriority_ReturnsSeverityOrder() throws Exception { - CrmDealStage stage = savedStage(); - CrmCompany company = savedCompany("Priority Sort V2 Corp"); - CrmContact contact = savedContact(company, "deal.priority.sort.v2@example.com"); - savedDealWithPriority("Priority High Deal", stage, company, contact, CrmDealPriority.HIGH); - savedDealWithPriority("Priority Low Deal", stage, company, contact, CrmDealPriority.LOW); - savedDealWithPriority("Priority Medium Deal", stage, company, contact, CrmDealPriority.MEDIUM); - - performRequest(get(BASE_PATH).param("companyId", company.getId().toString()) - .param("sortKey", "PRIORITY") - .accept(MediaType.APPLICATION_JSON)).andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath(STATUS_PATH).value(STATUS_SUCCESSFUL)) - .andExpect(jsonPath(RESULTS_0_PATH + "['items'].length()").value(3)) - .andExpect(jsonPath(RESULTS_0_PATH + "['items'][0]['priority']").value("LOW")) - .andExpect(jsonPath(RESULTS_0_PATH + "['items'][1]['priority']").value("MEDIUM")) - .andExpect(jsonPath(RESULTS_0_PATH + "['items'][2]['priority']").value("HIGH")); - } - -}