Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -7,6 +7,7 @@
import greenfirst.be.incentive.adapter.in.web.request.IncentiveListRequest;
import greenfirst.be.incentive.adapter.in.web.request.PartnerIncentiveListRequest;
import greenfirst.be.incentive.adapter.in.web.response.IncentiveListResponse;
import greenfirst.be.incentive.adapter.in.web.response.PartnerTotalIncentiveResponse;
import greenfirst.be.incentive.application.dto.in.IncentiveListInDto;
import greenfirst.be.incentive.application.dto.out.IncentiveListOutDto;
import greenfirst.be.incentive.application.service.GetIncentiveDataService;
Expand All @@ -21,9 +22,13 @@
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
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.RestController;

import java.math.BigDecimal;
import java.util.UUID;


/**
* 인센티브 조회 컨트롤러
Expand Down Expand Up @@ -137,4 +142,39 @@ public ResponseEntity<BaseResponse<IncentiveListResponse>> partnerGetIncentiveLi
return ResponseEntity.ok(new BaseResponse<>(response));
}


// 3. (admin) 특정 파트너의 총 인센티브 조회
@Operation(
summary = "파트너 총 인센티브 조회 (관리자)",
description = "특정 파트너의 누적 총 인센티브를 조회합니다.",
tags = { "Incentive - Admin" }
)
@GetMapping("/admin/total/{partnerUuid}")
@PreAuthorize("hasAuthority('ADMIN')")
@SecurityRequirement(name = "Bearer Auth")
public ResponseEntity<BaseResponse<PartnerTotalIncentiveResponse>> adminGetPartnerTotalIncentive(
@PathVariable UUID partnerUuid
) {
Comment on lines +155 to +157
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The admin endpoint is missing the authentication parameter that is consistently used in similar endpoints throughout the codebase. All other admin endpoints for partner data queries include CustomUserDetails authentication parameter (e.g., GetTotalStatsController.java:50-51, GetRelationshipDataController.java:44, GetDailyStatsController.java:169-172). This parameter is typically used for audit logging or additional authorization checks. Consider adding @AuthenticationPrincipal CustomUserDetails authentication to maintain consistency with established patterns.

Copilot uses AI. Check for mistakes.
BigDecimal totalIncentive = getIncentiveDataService.getTotalIncentiveByPartnerUuid(partnerUuid);
return ResponseEntity.ok(new BaseResponse<>(PartnerTotalIncentiveResponse.from(totalIncentive)));
}


// 4. (partner) 본인의 총 인센티브 조회
@Operation(
summary = "본인 총 인센티브 조회 (파트너)",
description = "로그인한 파트너 본인의 누적 총 인센티브를 조회합니다.",
tags = { "Incentive - Partner" }
)
@GetMapping("/partner/total")
@PreAuthorize("hasAnyAuthority('PERSONAL_PARTNER', 'CORPORATE_PARTNER')")
@SecurityRequirement(name = "Bearer Auth")
public ResponseEntity<BaseResponse<PartnerTotalIncentiveResponse>> partnerGetMyTotalIncentive(
@AuthenticationPrincipal CustomUserDetails authentication
) {
UUID partnerUuid = authentication.getUserUuid();
BigDecimal totalIncentive = getIncentiveDataService.getTotalIncentiveByPartnerUuid(partnerUuid);
return ResponseEntity.ok(new BaseResponse<>(PartnerTotalIncentiveResponse.from(totalIncentive)));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package greenfirst.be.incentive.adapter.in.web.response;


import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;


@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PartnerTotalIncentiveResponse {

private BigDecimal totalIncentive;


public static PartnerTotalIncentiveResponse from(BigDecimal totalIncentive) {
return PartnerTotalIncentiveResponse.builder()
.totalIncentive(totalIncentive != null ? totalIncentive : BigDecimal.ZERO)
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,21 @@ public Map<UUID, BigDecimal> getTotalIncentiveByPartnerUuids(List<UUID> partnerU
return result;
}

/**
* 단일 파트너의 총 인센티브 합계 조회 (EARN/REVERSAL 포함, 순합)
*/
public BigDecimal getTotalIncentiveByPartnerUuid(UUID partnerUuid) {
if (partnerUuid == null) {
return BigDecimal.ZERO;
}

BigDecimal result = queryFactory
.select(qIncentive.totalIncentive.sum())
.from(qIncentive)
.where(qIncentive.partnerUuid.eq(partnerUuid))
.fetchOne();

return result != null ? result : BigDecimal.ZERO;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ public Map<UUID, BigDecimal> getTotalIncentiveByPartnerUuids(List<UUID> partnerU
}


// 단일 파트너의 총 인센티브 합계 조회 (EARN/REVERSAL 포함, 순합)
@Override
public BigDecimal getTotalIncentiveByPartnerUuid(UUID partnerUuid) {
return incentiveQuerydslRepository.getTotalIncentiveByPartnerUuid(partnerUuid);
}


// 이미 REVERSAL이 존재하는지 확인 (idempotency)
@Override
public boolean existsByReversalOfIncentiveId(Long originalIncentiveId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ public interface IncentiveQueryRepository {
// 파트너 UUID 목록별 총 인센티브 합계 조회 (EARN/REVERSAL 포함, 순합)
Map<UUID, BigDecimal> getTotalIncentiveByPartnerUuids(List<UUID> partnerUuids);

// 단일 파트너의 총 인센티브 합계 조회 (EARN/REVERSAL 포함, 순합)
BigDecimal getTotalIncentiveByPartnerUuid(UUID partnerUuid);

// 이미 REVERSAL이 존재하는지 확인 (idempotency)
boolean existsByReversalOfIncentiveId(Long originalIncentiveId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.UUID;


/**
* 인센티브 조회 서비스
Expand Down Expand Up @@ -37,4 +40,14 @@ public IncentiveListOutDto getIncentiveList(IncentiveListInDto inDto) {
return incentiveQueryRepository.getIncentiveList(inDto);
}


/**
* 파트너의 총 인센티브 조회
* - 하위 추천인들을 통해 발생한 인센티브의 총합 (EARN + REVERSAL 순합)
*/
@Transactional(readOnly = true)
public BigDecimal getTotalIncentiveByPartnerUuid(UUID partnerUuid) {
return incentiveQueryRepository.getTotalIncentiveByPartnerUuid(partnerUuid);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,75 @@ void getIncentiveList_returnsRepositoryResult() {
}
}


@Nested
@DisplayName("getTotalIncentiveByPartnerUuid - 파트너 총 인센티브 조회")
class GetTotalIncentiveByPartnerUuidTest {

@Test
@DisplayName("성공: 파트너 UUID로 총 인센티브 합계를 조회한다")
void getTotalIncentiveByPartnerUuid_returnsTotalIncentive() {
// given
UUID partnerUuid = UUID.randomUUID();
BigDecimal expected = new BigDecimal("12345.67");
given(incentiveQueryRepository.getTotalIncentiveByPartnerUuid(partnerUuid))
.willReturn(expected);

// when
BigDecimal result = getIncentiveDataService.getTotalIncentiveByPartnerUuid(partnerUuid);

// then
assertThat(result).isEqualTo(expected);
verify(incentiveQueryRepository).getTotalIncentiveByPartnerUuid(partnerUuid);
}

@Test
@DisplayName("성공: 인센티브가 없는 파트너는 ZERO를 반환한다")
void getTotalIncentiveByPartnerUuid_returnsZeroWhenNoIncentive() {
// given
UUID partnerUuid = UUID.randomUUID();
given(incentiveQueryRepository.getTotalIncentiveByPartnerUuid(partnerUuid))
.willReturn(BigDecimal.ZERO);

// when
BigDecimal result = getIncentiveDataService.getTotalIncentiveByPartnerUuid(partnerUuid);

// then
assertThat(result).isEqualTo(BigDecimal.ZERO);
verify(incentiveQueryRepository).getTotalIncentiveByPartnerUuid(partnerUuid);
}

@Test
@DisplayName("성공: 음수 인센티브(REVERSAL 합계가 더 큰 경우)도 그대로 반환한다")
void getTotalIncentiveByPartnerUuid_returnsNegativeWhenReversalExceedsEarn() {
// given
UUID partnerUuid = UUID.randomUUID();
BigDecimal expected = new BigDecimal("-500.00");
given(incentiveQueryRepository.getTotalIncentiveByPartnerUuid(partnerUuid))
.willReturn(expected);

// when
BigDecimal result = getIncentiveDataService.getTotalIncentiveByPartnerUuid(partnerUuid);

// then
assertThat(result).isEqualTo(expected);
verify(incentiveQueryRepository).getTotalIncentiveByPartnerUuid(partnerUuid);
}

@Test
@DisplayName("성공: null UUID 입력 시 ZERO를 반환한다")
void getTotalIncentiveByPartnerUuid_returnsZeroForNullUuid() {
// given
given(incentiveQueryRepository.getTotalIncentiveByPartnerUuid(null))
.willReturn(BigDecimal.ZERO);

// when
BigDecimal result = getIncentiveDataService.getTotalIncentiveByPartnerUuid(null);

// then
assertThat(result).isEqualTo(BigDecimal.ZERO);
verify(incentiveQueryRepository).getTotalIncentiveByPartnerUuid(null);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,17 @@ public Map<UUID, BigDecimal> getTotalIncentiveByPartnerUuids(List<UUID> partnerU
return Map.of();
}

@Override
public BigDecimal getTotalIncentiveByPartnerUuid(UUID partnerUuid) {
if (partnerUuid == null) {
return BigDecimal.ZERO;
}
return storage.values().stream()
.filter(incentive -> Objects.equals(incentive.getPartnerUuid(), partnerUuid))
.map(Incentive::getTotalIncentive)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}

@Override
public boolean existsByReversalOfIncentiveId(Long originalIncentiveId) {
return storage.values().stream()
Expand Down
Loading