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 @@ -5,18 +5,24 @@
import lombok.NoArgsConstructor;

/**
* plannedHours: 해당 월 ALLOCATION_GOAL(잡별 추천 근무시간) 합
* actualHours: 해당 월 WORK_LOG 전체(PLANNED+CONFIRMED) 근무시간 합
* expectedIncome: 전체 WORK_LOG의 estimated_income 합
* goalHours: 해당 월 ALLOCATION_GOAL(잡별 추천 근무시간) 합
* confirmedHours/scheduledHours: 해당 월 WORK_LOG를 status(CONFIRMED/PLANNED) 기준으로 나눈 근무시간 합
* expectedIncome: 전체 WORK_LOG의 estimated_income 합 (status/settlementStatus 무관)
* expectedSettlementIncome: 예상 정산 소득. WORK_LOG_PLATFORM_INCOME 기준으로 아직 COMPLETED가 안 된
* 플랫폼 몫 + PLANNED WORK_LOG 전체(estimated_income). 실제 입금액(actualIncome)은 SETTLEMENT
* 테이블 기반이라 WorkLog 데이터만으로는 계산할 수 없어 이 DTO에는 포함하지 않는다 - 필요하면
* IncomeAnalysisQueryClient(§ SETTLEMENT 집계)를 별도로 조회해야 한다.
* targetAmount: 해당 월 SAVING_GOAL.target_amount. 저축 목표를 등록하지 않은 달은 null
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class CalendarMonthlyHours {

private int plannedHours;
private int actualHours;
private int goalHours;
private int confirmedHours;
private int scheduledHours;
private Long expectedIncome;
private Long expectedSettlementIncome;
private Long targetAmount;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import com.ntropy.bff.dto.dashboard.response.DashboardHoursResponse;
import com.ntropy.bff.dto.dashboard.response.DashboardIncomeResponse;
import com.ntropy.bff.dto.dashboard.response.DashboardResponse;
import com.ntropy.bff.dto.dashboard.response.DashboardSettlementResponse;
import com.ntropy.bff.dto.dashboard.response.JobRecommendationsResponse;
import com.ntropy.bff.security.AuthenticatedUserIdResolver;
import com.ntropy.common.client.CalendarQueryClient;
Expand Down Expand Up @@ -72,9 +71,8 @@ public ApiResponse<DashboardResponse> getDashboard(@ApiParam(hidden = true) Auth

DashboardResponse response = new DashboardResponse(
user.name(),
new DashboardHoursResponse(hours.getActualHours(), hours.getPlannedHours()),
new DashboardIncomeResponse(incomeAnalysis.getTotalIncome(), hours.getTargetAmount()),
DashboardSettlementResponse.from(incomeAnalysis.getEarnedDepositComparisons()),
new DashboardHoursResponse(hours.getConfirmedHours(), hours.getScheduledHours(), hours.getGoalHours()),
new DashboardIncomeResponse(incomeAnalysis.getTotalIncome(), hours.getExpectedSettlementIncome(), hours.getTargetAmount()),
fatigueScore
);
return ApiResponse.success(response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@AllArgsConstructor
public class DashboardHoursResponse {

private int current;
private int target;
private int confirmedHours;
private int scheduledHours;
private int goalHours;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@
import lombok.AllArgsConstructor;
import lombok.Getter;

/**
* actualIncome: 이번 달 실제 입금액. SETTLEMENT(MATCHED, deposit_date 기준) 합계 - 진짜 통장에 들어온 돈.
* expectedSettlementIncome: 예상 정산 소득. WORK_LOG/WORK_LOG_PLATFORM_INCOME 기준 아직 정산 안 된 예상치.
* goalIncome: 이번 달 SAVING_GOAL.target_amount.
*/
@Getter
@AllArgsConstructor
public class DashboardIncomeResponse {

private Long amount;
private Long target;
private Long actualIncome;
private Long expectedSettlementIncome;
private Long goalIncome;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,5 @@ public class DashboardResponse {
private String greetingName;
private DashboardHoursResponse goalHours;
private DashboardIncomeResponse goalIncome;
private DashboardSettlementResponse settlement;
private Integer fatigueScore;
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.ntropy.work.domain;

/**
* WorkLog.status 값. WorkLogService, WorkLogReminderService, CalendarService 등
* 여러 서비스에서 공통으로 참조하는 문자열 상수다.
*/
public final class WorkLogStatus {

public static final String PLANNED = "PLANNED";
public static final String CONFIRMED = "CONFIRMED";

private WorkLogStatus() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,17 @@
import com.ntropy.common.dto.work.summary.CalendarWorkBrief;
import com.ntropy.common.dto.work.summary.WeatherForecast;
import com.ntropy.common.dto.work.summary.WeatherForecastList;
import com.ntropy.work.domain.WorkLogStatus;
import com.ntropy.work.domain.entity.AllocationGoal;
import com.ntropy.work.domain.entity.Job;
import com.ntropy.work.domain.entity.SavingGoal;
import com.ntropy.work.domain.entity.WorkLog;
import com.ntropy.work.domain.entity.WorkLogPlatformIncome;
import com.ntropy.work.domain.enums.SettlementStatus;
import com.ntropy.work.mapper.AllocationGoalMapper;
import com.ntropy.work.mapper.SavingGoalMapper;
import com.ntropy.work.mapper.WorkLogMapper;
import com.ntropy.work.mapper.WorkLogPlatformIncomeMapper;
import com.ntropy.work.util.WorkTimeUtils;

import lombok.RequiredArgsConstructor;
Expand All @@ -56,6 +59,7 @@ public class CalendarService {
}

private final WorkLogMapper workLogMapper;
private final WorkLogPlatformIncomeMapper workLogPlatformIncomeMapper;
private final AllocationGoalMapper allocationGoalMapper;
private final SavingGoalMapper savingGoalMapper;
private final JobService jobService;
Expand All @@ -74,14 +78,16 @@ public CalendarMonthlySummary getMonthlySummary(Long userId, int year, int month
List<Long> jobIds = jobs.stream().map(Job::getJobId).collect(Collectors.toList());

List<WorkLog> workLogs = workLogMapper.findByUserIdAndDateRange(userId, startDate, endDate);
List<WorkLogPlatformIncome> confirmedIncomes =
workLogPlatformIncomeMapper.findConfirmedByUserIdAndDateRange(userId, startDate, endDate);
List<AllocationGoal> allocationGoals = jobIds.isEmpty()
? List.of()
: allocationGoalMapper.findByJobIdsAndTargetMonth(jobIds, targetMonth);
SavingGoal savingGoal = savingGoalMapper.findByUserIdAndTargetMonth(userId, targetMonth);

Map<LocalDate, WeatherForecast> weatherByDate = weatherByDate(latitude, longitude);

CalendarMonthlyHours hours = summarizeHours(workLogs, allocationGoals, savingGoal);
CalendarMonthlyHours hours = summarizeHours(workLogs, confirmedIncomes, allocationGoals, savingGoal);
List<CalendarDaySummary> days = summarizeDays(workLogs, jobNames, weatherByDate);

return new CalendarMonthlySummary(year, month, hours, days);
Expand Down Expand Up @@ -134,27 +140,44 @@ private Map<LocalDate, WeatherForecast> weatherByDate(Double latitude, Double lo
}

/**
* plannedHours: 해당 월 ALLOCATION_GOAL(잡별 추천 근무시간) 합
* actualHours: 해당 월 WORK_LOG 전체(PLANNED+CONFIRMED) 근무시간 합
* goalHours: 해당 월 ALLOCATION_GOAL(잡별 추천 근무시간) 합
* confirmedHours/scheduledHours: 해당 월 WORK_LOG를 status(CONFIRMED/PLANNED) 기준으로 나눈 근무시간 합
* expectedSettlementIncome: expectedIncome(전체 예상소득) - CONFIRMED 근무일지에 딸린
* WORK_LOG_PLATFORM_INCOME 행 중 이미 COMPLETED로 정산 완료된 행들의 expectedAmount 합.
* 플랫폼별 income 행 단위로 계산하므로, 여러 플랫폼 중 일부만 정산된 PARTIAL 근무일지도
* 완료된 플랫폼 몫만 정확히 제외된다. 실제 입금액(actualIncome)은 SETTLEMENT 테이블
* 기반이라 여기서는 계산하지 않는다 - IncomeAnalysisQueryClient를 따로 써야 한다.
* targetAmount: 해당 월 SAVING_GOAL이 없으면 null (달성률 미표시는 프론트 책임)
*/
private CalendarMonthlyHours summarizeHours(List<WorkLog> workLogs, List<AllocationGoal> allocationGoals,
SavingGoal savingGoal) {
int plannedHours = allocationGoals.stream()
private CalendarMonthlyHours summarizeHours(List<WorkLog> workLogs, List<WorkLogPlatformIncome> confirmedIncomes,
List<AllocationGoal> allocationGoals, SavingGoal savingGoal) {
int goalHours = allocationGoals.stream()
.mapToInt(goal -> goal.getRecommendHour() == null ? 0 : goal.getRecommendHour().intValue())
.sum();

int actualHours = 0;
int confirmedHours = 0;
int scheduledHours = 0;
long expectedIncome = 0;
for (WorkLog workLog : workLogs) {
actualHours += WorkTimeUtils.durationHours(workLog.getStartTime(), workLog.getEndTime());
if (workLog.getEstimatedIncome() != null) {
expectedIncome += workLog.getEstimatedIncome();
int hours = WorkTimeUtils.durationHours(workLog.getStartTime(), workLog.getEndTime());
long income = workLog.getEstimatedIncome() == null ? 0 : workLog.getEstimatedIncome();
if (WorkLogStatus.CONFIRMED.equals(workLog.getStatus())) {
confirmedHours += hours;
} else if (WorkLogStatus.PLANNED.equals(workLog.getStatus())) {
scheduledHours += hours;
}
expectedIncome += income;
}

long completedFromPlatforms = confirmedIncomes.stream()
.filter(income -> SettlementStatus.COMPLETED.equals(income.getSettlementStatus()))
.mapToLong(income -> income.getExpectedAmount() == null ? 0 : income.getExpectedAmount())
.sum();
long expectedSettlementIncome = expectedIncome - completedFromPlatforms;

Long targetAmount = savingGoal == null ? null : savingGoal.getTargetAmount();
return new CalendarMonthlyHours(plannedHours, actualHours, expectedIncome, targetAmount);
return new CalendarMonthlyHours(goalHours, confirmedHours, scheduledHours, expectedIncome,
expectedSettlementIncome, targetAmount);
}

private List<CalendarDaySummary> summarizeDays(List<WorkLog> workLogs, Map<Long, String> jobNames,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.ntropy.common.client.NotificationCommandClient;
import com.ntropy.common.dto.notification.NotificationCreateCommand;
import com.ntropy.work.config.WorkReminderBatchUserScopeProperties;
import com.ntropy.work.domain.WorkLogStatus;
import com.ntropy.work.domain.entity.WorkLog;
import com.ntropy.work.mapper.WorkLogMapper;

Expand All @@ -27,7 +28,7 @@
@RequiredArgsConstructor
public class WorkLogReminderService {

private static final String STATUS_PLANNED = "PLANNED";
private static final String STATUS_PLANNED = WorkLogStatus.PLANNED;
private static final int UNCONFIRMED_REMINDER_DELAY_MINUTES = 60;

private final ActiveUserQueryClient activeUserQueryClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import com.ntropy.common.exception.ServiceException;
import com.ntropy.work.domain.WorkLogSettlementStatusCalculator;
import com.ntropy.work.domain.WorkLogStatus;
import com.ntropy.work.domain.entity.Job;
import com.ntropy.work.domain.entity.JobPlatformMapping;
import com.ntropy.work.domain.entity.Platform;
Expand All @@ -30,8 +31,8 @@
@RequiredArgsConstructor
public class WorkLogService {

private static final String STATUS_PLANNED = "PLANNED";
private static final String STATUS_CONFIRMED = "CONFIRMED";
private static final String STATUS_PLANNED = WorkLogStatus.PLANNED;
private static final String STATUS_CONFIRMED = WorkLogStatus.CONFIRMED;
private static final String TRIGGER_ON_DEMAND = "ON_DEMAND";

private final WorkLogMapper workLogMapper;
Expand Down
Loading
Loading