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 @@ -17,4 +17,6 @@ public class CalendarWorkBrief {
private LocalTime startTime;
private LocalTime endTime;
private String status;
private Long taskCount;
private Long fatigue;
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public enum WorkErrorCode implements ServiceErrorCode {
WORK_LOG_ACCESS_DENIED(403, "본인 소유가 아닌 근무일지입니다."),
INVALID_WORK_TIME_RANGE(400, "시작 시간과 종료 시간이 같을 수 없습니다."),
WORK_LOG_ALREADY_CONFIRMED(409, "이미 확정된 근무일지입니다."),
WORK_LOG_SETTLEMENT_IN_PROGRESS(409, "정산이 진행 중이거나 완료된 근무일지는 수정할 수 없습니다."),

CATEGORY_NOT_FOUND(404, "존재하지 않는 카테고리입니다."),
PLATFORM_NOT_FOUND(404, "존재하지 않는 플랫폼입니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public CalendarDailySummary getDailySummary(Long userId, LocalDate date, Double

List<CalendarWorkBrief> works = workLogs.stream()
.map(w -> new CalendarWorkBrief(w.getLogId(), w.getJobId(), jobNames.get(w.getJobId()),
w.getStartTime(), w.getEndTime(), w.getStatus()))
w.getStartTime(), w.getEndTime(), w.getStatus(), w.getTaskCount(), w.getFatigue()))
.collect(Collectors.toList());

String dayOfWeek = KOREAN_DAY_OF_WEEK.get(date.getDayOfWeek());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,11 @@ public WorkLog registerActual(WorkLog workLog) {
}

/**
* 근무일지 수정. PLANNED 상태에서만 가능하다. 확정(CONFIRMED) 이후에는
* WORK_LOG_PLATFORM_INCOME과의 정합성이 깨질 수 있어 수정할 수 없고, 삭제만 가능하다.
* 근무일지 수정. PLANNED 상태는 항상 가능하다. CONFIRMED 상태는 아직 어느 플랫폼도
* 정산되지 않은 경우(settlementStatus == PENDING)에 한해 허용한다 - 하나라도
* 실제 입금과 매칭(PARTIAL/COMPLETED)된 이후에는 그 기록을 보존해야 하므로 여전히
* 수정할 수 없고 삭제만 가능하다. CONFIRMED 상태를 수정하면 소득이 바뀔 수 있어
* WORK_LOG_PLATFORM_INCOME을 지우고 다시 배분한다.
* 넘어온 필드만 덮어쓰고 나머지는 기존 값을 유지한다.
*
* @param requesterUserId 요청자 userId. 근무일지 소유자와 다르면 예외
Expand All @@ -99,18 +102,30 @@ public WorkLog registerActual(WorkLog workLog) {
public WorkLog editWorkLog(Long requesterUserId, Long logId, WorkLog patch) {
WorkLog existing = findById(logId);
verifyOwnership(requesterUserId, existing.getUserId(), logId);
if (STATUS_CONFIRMED.equals(existing.getStatus())) {
throw new ServiceException(WorkErrorCode.WORK_LOG_ALREADY_CONFIRMED, "logId=" + logId);
boolean wasConfirmed = STATUS_CONFIRMED.equals(existing.getStatus());
if (wasConfirmed && existing.getSettlementStatus() != SettlementStatus.PENDING) {
throw new ServiceException(WorkErrorCode.WORK_LOG_SETTLEMENT_IN_PROGRESS, "logId=" + logId);
}
applyPatch(existing, patch);
validateNoOverlap(existing.getUserId(), existing.getWorkDate(), existing.getStartTime(), existing.getEndTime(),
existing.getLogId());

Job job = jobService.findById(existing.getJobId());
if (wasConfirmed) {
validateTaskCountIfPerTask(job, existing.getTaskCount());
}
existing.setEstimatedIncome(
calculateEstimatedIncome(job, existing.getStartTime(), existing.getEndTime(), existing.getTaskCount()));

workLogMapper.update(existing);
if (wasConfirmed) {
workLogPlatformIncomeMapper.deleteByLogId(existing.getLogId());
List<WorkLogPlatformIncome> incomes = resolvePlatformIncomes(job.getJobId(), existing.getEstimatedIncome());
existing.setSettlementStatus(WorkLogSettlementStatusCalculator.calculate(incomes));
workLogMapper.update(existing);
saveIncomes(existing.getLogId(), incomes);
} else {
workLogMapper.update(existing);
}
return existing;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,14 +255,17 @@ void monthlySummary_savingGoalForOtherMonth_targetAmountIsNull() {
@DisplayName("일간 요약은 근무 목록과 요일 한글 표기를 채운다")
void dailySummary_returnsWorksAndKoreanDayOfWeek() {
LocalDate monday = LocalDate.of(2026, 8, 3);
seedWorkLog(JOB_A, monday, LocalTime.of(18, 0), LocalTime.of(22, 0),
WorkLog log = seedWorkLog(JOB_A, monday, LocalTime.of(18, 0), LocalTime.of(22, 0),
"PLANNED", SettlementStatus.NONE, 48000L);
log.setFatigue(3L);
workLogMapper.update(log);

CalendarDailySummary summary = calendarService.getDailySummary(USER_ID, monday, null, null);

assertEquals("월", summary.getDayOfWeek());
assertEquals(1, summary.getWorks().size());
assertEquals("배민 배달", summary.getWorks().get(0).getJobName());
assertEquals(3L, summary.getWorks().get(0).getFatigue());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,17 +377,50 @@ void editWorkLog_notOwner_throws() {
}

@Test
@DisplayName("확정된 근무일지는 수정할 수 없다")
void editWorkLog_confirmed_throws() {
Job job = hourlyJob();
WorkLog plan = workLogService.registerPlan(planOf(job.getJobId(), LocalTime.of(18, 0), LocalTime.of(22, 0)));
@DisplayName("정산이 진행된(COMPLETED) 확정 근무일지는 수정할 수 없다")
void editWorkLog_settlementCompleted_throws() {
Job job = onDemandJob(); // ON_DEMAND 플랫폼이라 확정 즉시 COMPLETED
WorkLog plan = workLogService.registerPlan(planOf(job.getJobId(), LocalTime.of(20, 0), LocalTime.of(22, 0)));
workLogService.confirmWorkLog(USER_ID, plan.getLogId(), WorkLog.builder().build());

WorkLog patch = WorkLog.builder().endTime(LocalTime.of(23, 0)).build();

assertThrows(ServiceException.class, () -> workLogService.editWorkLog(USER_ID, plan.getLogId(), patch));
}

@Test
@DisplayName("확정됐지만 아직 정산이 시작되지 않은(PENDING) 근무일지는 수정할 수 있다")
void editWorkLog_confirmedButPending_isAllowed() {
Job job = hourlyJob(); // 플랫폼 매핑이 없어 확정해도 PENDING 유지
WorkLog plan = workLogService.registerPlan(planOf(job.getJobId(), LocalTime.of(18, 0), LocalTime.of(22, 0)));
workLogService.confirmWorkLog(USER_ID, plan.getLogId(), WorkLog.builder().build());

WorkLog patch = WorkLog.builder().endTime(LocalTime.of(23, 0)).build();
WorkLog result = workLogService.editWorkLog(USER_ID, plan.getLogId(), patch);

assertEquals("CONFIRMED", result.getStatus());
assertEquals(LocalTime.of(23, 0), result.getEndTime());
assertEquals(60000L, result.getEstimatedIncome());
}

@Test
@DisplayName("자동정산 플랫폼 잡을 확정 후(PENDING) 수정하면 income 행이 새 소득 기준으로 재생성된다")
void editWorkLog_autoPlatformPending_regeneratesIncomeRows() {
Job job = multiPlatformJob(); // AUTO 트리거 플랫폼 2개 매핑, 확정해도 PENDING 유지
WorkLog plan = workLogService.registerPlan(planOf(job.getJobId(), LocalTime.of(18, 0), LocalTime.of(22, 0)));
WorkLog confirmed = workLogService.confirmWorkLog(USER_ID, plan.getLogId(), WorkLog.builder().build());
assertEquals(SettlementStatus.PENDING, confirmed.getSettlementStatus());

WorkLog patch = WorkLog.builder().endTime(LocalTime.of(23, 0)).build(); // 4시간 -> 5시간, 40000 -> 50000
WorkLog result = workLogService.editWorkLog(USER_ID, plan.getLogId(), patch);

assertEquals(50000L, result.getEstimatedIncome());
List<WorkLogPlatformIncome> incomes = workLogPlatformIncomeMapper.findByLogId(result.getLogId());
assertEquals(2, incomes.size());
long total = incomes.stream().mapToLong(WorkLogPlatformIncome::getExpectedAmount).sum();
assertEquals(50000L, total);
}

@Test
@DisplayName("확정하면 PLANNED에서 CONFIRMED로 전환된다")
void confirmWorkLog_transitionsPlannedToConfirmed() {
Expand Down
Loading