|
| 1 | +package com.swyp.picke.domain.admin.adfit; |
| 2 | + |
| 3 | +import java.math.BigDecimal; |
| 4 | +import java.math.RoundingMode; |
| 5 | +import java.time.LocalDate; |
| 6 | +import java.time.ZoneId; |
| 7 | +import java.time.temporal.ChronoUnit; |
| 8 | +import java.util.Arrays; |
| 9 | +import java.util.List; |
| 10 | +import lombok.RequiredArgsConstructor; |
| 11 | +import org.springframework.stereotype.Service; |
| 12 | +import org.springframework.transaction.annotation.Transactional; |
| 13 | + |
| 14 | +@Service |
| 15 | +@RequiredArgsConstructor |
| 16 | +public class AdfitReportService { |
| 17 | + private final AdfitDailyRepository repository; |
| 18 | + |
| 19 | + @Transactional |
| 20 | + public void save(AdfitDailyRequest request) { |
| 21 | + if (request.date().isAfter(LocalDate.now(ZoneId.of("Asia/Seoul")))) { |
| 22 | + throw new IllegalArgumentException("미래 날짜의 수익은 입력할 수 없습니다."); |
| 23 | + } |
| 24 | + AdfitDaily daily = repository.findByDateAndUnit(request.date(), request.unit()) |
| 25 | + .orElseGet(() -> new AdfitDaily(request.date(), request.unit())); |
| 26 | + daily.update(request.revenue(), request.cost(), request.costBasis()); |
| 27 | + repository.save(daily); |
| 28 | + } |
| 29 | + |
| 30 | + @Transactional(readOnly = true) |
| 31 | + public AdfitReport report(LocalDate from, LocalDate to) { |
| 32 | + long expected = ChronoUnit.DAYS.between(from, to) + 1; |
| 33 | + if (expected < 1 || expected > 366) { |
| 34 | + throw new IllegalArgumentException("조회 기간은 1일부터 366일까지입니다."); |
| 35 | + } |
| 36 | + List<AdfitDaily> days = repository.findAllByDateBetweenOrderByDateDesc(from, to); |
| 37 | + List<AdfitReport.UnitReport> units = Arrays.stream(AdfitUnit.values()).map(unit -> { |
| 38 | + List<AdfitDaily> entries = days.stream().filter(day -> day.getUnit() == unit).toList(); |
| 39 | + BigDecimal revenue = entries.isEmpty() ? null : entries.stream().map(AdfitDaily::getRevenue) |
| 40 | + .reduce(BigDecimal.ZERO, BigDecimal::add); |
| 41 | + BigDecimal cost = entries.isEmpty() ? null : entries.stream().map(AdfitDaily::getCost) |
| 42 | + .reduce(BigDecimal.ZERO, BigDecimal::add); |
| 43 | + // 기간이 빠졌거나 비용 기준이 섞이면 비교 가능한 ROI가 아니다. |
| 44 | + boolean complete = entries.size() == expected |
| 45 | + && entries.stream().map(AdfitDaily::getCostBasis).distinct().count() == 1; |
| 46 | + BigDecimal roi = complete && cost != null && cost.signum() > 0 |
| 47 | + ? revenue.subtract(cost).multiply(BigDecimal.valueOf(100)) |
| 48 | + .divide(cost, 2, RoundingMode.HALF_UP) : null; |
| 49 | + return new AdfitReport.UnitReport(unit, unit.getDisplayName(), unit.getPlacements(), |
| 50 | + unit.getFormat(), entries.size(), expected, revenue, cost, roi); |
| 51 | + }).toList(); |
| 52 | + return new AdfitReport(from, to, "MANUAL_CONSOLE", units, days.stream().map(day -> |
| 53 | + new AdfitReport.Day(day.getDate(), day.getUnit(), day.getRevenue(), day.getCost(), |
| 54 | + day.getCostBasis())).toList()); |
| 55 | + } |
| 56 | +} |
0 commit comments