feat : thêm bao cao
This commit is contained in:
parent
f9626aa701
commit
3c055be676
|
|
@ -2,6 +2,7 @@ package com.vega.hrm.core.repositories;
|
||||||
|
|
||||||
import com.vega.hrm.core.entities.RevenueData;
|
import com.vega.hrm.core.entities.RevenueData;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
|
|
@ -24,4 +25,13 @@ public interface RevenueDataRepository extends JpaRepository<RevenueData, UUID>
|
||||||
);
|
);
|
||||||
|
|
||||||
RevenueData findByEmailAndRevenueDate(String email, LocalDate revenueDate);
|
RevenueData findByEmailAndRevenueDate(String email, LocalDate revenueDate);
|
||||||
|
|
||||||
|
@Query("SELECT r FROM RevenueData r WHERE r.email = :email " +
|
||||||
|
"AND r.revenueDate >= :startDate AND r.revenueDate <= :endDate " +
|
||||||
|
"ORDER BY r.revenueDate ASC")
|
||||||
|
List<RevenueData> findAllByEmailAndDateRange(
|
||||||
|
@Param("email") String email,
|
||||||
|
@Param("startDate") LocalDate startDate,
|
||||||
|
@Param("endDate") LocalDate endDate
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,15 @@ import com.vega.hrm.core.component.TokenStore;
|
||||||
import com.vega.hrm.core.dto.GoogleOAuthConfig;
|
import com.vega.hrm.core.dto.GoogleOAuthConfig;
|
||||||
import com.vega.hrm.core.models.responses.BaseResponse;
|
import com.vega.hrm.core.models.responses.BaseResponse;
|
||||||
import com.vega.hrm.core.models.responses.PagedListResponse;
|
import com.vega.hrm.core.models.responses.PagedListResponse;
|
||||||
|
import com.vega.hrm.report.request.ChannelReportRequest;
|
||||||
import com.vega.hrm.report.request.GetDragRevenueRequest;
|
import com.vega.hrm.report.request.GetDragRevenueRequest;
|
||||||
import com.vega.hrm.report.request.GetRevenueRequest;
|
import com.vega.hrm.report.request.GetRevenueRequest;
|
||||||
|
import com.vega.hrm.report.response.ChannelActivityReportDto;
|
||||||
|
import com.vega.hrm.report.response.ChannelRevenueReportDto;
|
||||||
import com.vega.hrm.report.response.ReportTypeDto;
|
import com.vega.hrm.report.response.ReportTypeDto;
|
||||||
import com.vega.hrm.report.response.RevenueDataDto;
|
import com.vega.hrm.report.response.RevenueDataDto;
|
||||||
|
import com.vega.hrm.report.serivce.ChannelActivityReportService;
|
||||||
|
import com.vega.hrm.report.serivce.ChannelRevenueReportService;
|
||||||
import com.vega.hrm.report.serivce.CreateReportingJobService;
|
import com.vega.hrm.report.serivce.CreateReportingJobService;
|
||||||
import com.vega.hrm.report.serivce.RevenueDataService;
|
import com.vega.hrm.report.serivce.RevenueDataService;
|
||||||
import com.vega.hrm.report.serivce.YouTubeReportService;
|
import com.vega.hrm.report.serivce.YouTubeReportService;
|
||||||
|
|
@ -42,6 +47,8 @@ public class ReportGoogleController {
|
||||||
private final CreateReportingJobService createReportingJob;
|
private final CreateReportingJobService createReportingJob;
|
||||||
private final YouTubeReportService youTubeReportService;
|
private final YouTubeReportService youTubeReportService;
|
||||||
private final RevenueDataService revenueDataService;
|
private final RevenueDataService revenueDataService;
|
||||||
|
private final ChannelRevenueReportService channelRevenueReportService;
|
||||||
|
private final ChannelActivityReportService channelActivityReportService;
|
||||||
private final GoogleOAuthConfig googleOAuthConfig;
|
private final GoogleOAuthConfig googleOAuthConfig;
|
||||||
private final TokenStore tokenStore;
|
private final TokenStore tokenStore;
|
||||||
|
|
||||||
|
|
@ -140,4 +147,68 @@ public class ReportGoogleController {
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/channel/revenue")
|
||||||
|
@Operation(summary = "Báo cáo doanh thu theo kênh", description = "Tổng hợp báo cáo doanh thu theo kênh từ database với các thống kê và tổng hợp theo tuần/tháng.")
|
||||||
|
public ResponseEntity<BaseResponse<ChannelRevenueReportDto>> getChannelRevenueReport(
|
||||||
|
@Valid @RequestBody ChannelReportRequest request
|
||||||
|
) {
|
||||||
|
if (tokenStore.getTokenResponse(request.getEmail()) == null) {
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
BaseResponse.<ChannelRevenueReportDto>builder()
|
||||||
|
.code("01")
|
||||||
|
.message("Không tìm thấy token Google cho email đã cung cấp")
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelRevenueReportDto report = channelRevenueReportService.generateRevenueReport(
|
||||||
|
request.getEmail(),
|
||||||
|
request.getStartDate(),
|
||||||
|
request.getEndDate(),
|
||||||
|
request.getIncludeWeeklySummary() != null && request.getIncludeWeeklySummary(),
|
||||||
|
request.getIncludeMonthlySummary() != null && request.getIncludeMonthlySummary()
|
||||||
|
);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
BaseResponse.<ChannelRevenueReportDto>builder()
|
||||||
|
.code("00")
|
||||||
|
.message("Lấy báo cáo doanh thu theo kênh thành công")
|
||||||
|
.data(report)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/channel/activity")
|
||||||
|
@Operation(summary = "Báo cáo hoạt động theo kênh", description = "Lấy báo cáo hoạt động theo kênh từ YouTube Analytics API bao gồm views, watch time, subscribers, likes, comments.")
|
||||||
|
public ResponseEntity<BaseResponse<ChannelActivityReportDto>> getChannelActivityReport(
|
||||||
|
@Valid @RequestBody ChannelReportRequest request
|
||||||
|
) throws GeneralSecurityException, IOException {
|
||||||
|
if (tokenStore.getTokenResponse(request.getEmail()) == null) {
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
BaseResponse.<ChannelActivityReportDto>builder()
|
||||||
|
.code("01")
|
||||||
|
.message("Không tìm thấy token Google cho email đã cung cấp")
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelActivityReportDto report = channelActivityReportService.getChannelActivityReport(
|
||||||
|
request.getEmail(),
|
||||||
|
request.getStartDate(),
|
||||||
|
request.getEndDate(),
|
||||||
|
GoogleNetHttpTransport.newTrustedTransport(),
|
||||||
|
JacksonFactory.getDefaultInstance(),
|
||||||
|
request.getIncludeWeeklySummary() != null && request.getIncludeWeeklySummary(),
|
||||||
|
request.getIncludeMonthlySummary() != null && request.getIncludeMonthlySummary()
|
||||||
|
);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
BaseResponse.<ChannelActivityReportDto>builder()
|
||||||
|
.code("00")
|
||||||
|
.message("Lấy báo cáo hoạt động theo kênh thành công")
|
||||||
|
.data(report)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.vega.hrm.report.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class ChannelReportRequest {
|
||||||
|
@NotBlank(message = "Email không được để trống")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@NotBlank(message = "Ngày bắt đầu không được để trống")
|
||||||
|
private String startDate; // format: YYYY-MM-DD
|
||||||
|
|
||||||
|
@NotBlank(message = "Ngày kết thúc không được để trống")
|
||||||
|
private String endDate; // format: YYYY-MM-DD
|
||||||
|
|
||||||
|
// Optional: Group by period
|
||||||
|
private Boolean includeWeeklySummary = false;
|
||||||
|
private Boolean includeMonthlySummary = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.vega.hrm.report.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ActivityDataDto {
|
||||||
|
private String date;
|
||||||
|
private Long views;
|
||||||
|
private Long watchTime; // seconds
|
||||||
|
private Long subscribers;
|
||||||
|
private Long likes;
|
||||||
|
private Long comments;
|
||||||
|
private Long shares;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.vega.hrm.report.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ActivitySummaryDto {
|
||||||
|
private String period; // "2025-W01" cho tuần, "2025-01" cho tháng
|
||||||
|
private Long totalViews;
|
||||||
|
private Long totalWatchTime;
|
||||||
|
private Long totalSubscribers;
|
||||||
|
private Long totalLikes;
|
||||||
|
private Long totalComments;
|
||||||
|
private Integer dayCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.vega.hrm.report.response;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ChannelActivityReportDto {
|
||||||
|
private String email;
|
||||||
|
private String startDate;
|
||||||
|
private String endDate;
|
||||||
|
|
||||||
|
// Tổng hợp metrics
|
||||||
|
private Long totalViews;
|
||||||
|
private Long totalWatchTime; // seconds
|
||||||
|
private Long totalSubscribers;
|
||||||
|
private Long totalLikes;
|
||||||
|
private Long totalComments;
|
||||||
|
|
||||||
|
// Trung bình
|
||||||
|
private Double averageViews;
|
||||||
|
private Double averageWatchTime;
|
||||||
|
private Double averageSubscribers;
|
||||||
|
|
||||||
|
// Thống kê
|
||||||
|
private Integer totalDays;
|
||||||
|
|
||||||
|
// Dữ liệu chi tiết theo ngày
|
||||||
|
private List<ActivityDataDto> dailyActivity;
|
||||||
|
|
||||||
|
// Tổng hợp theo tuần/tháng
|
||||||
|
private List<ActivitySummaryDto> weeklySummary;
|
||||||
|
private List<ActivitySummaryDto> monthlySummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.vega.hrm.report.response;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ChannelRevenueReportDto {
|
||||||
|
private String email;
|
||||||
|
private String startDate;
|
||||||
|
private String endDate;
|
||||||
|
|
||||||
|
// Tổng hợp doanh thu
|
||||||
|
private Double totalRevenue;
|
||||||
|
private Double averageDailyRevenue;
|
||||||
|
private Double maxDailyRevenue;
|
||||||
|
private Double minDailyRevenue;
|
||||||
|
|
||||||
|
// Thống kê
|
||||||
|
private Integer totalDays;
|
||||||
|
private Integer daysWithRevenue;
|
||||||
|
|
||||||
|
// Dữ liệu chi tiết theo ngày
|
||||||
|
private List<RevenueDataDto> dailyRevenue;
|
||||||
|
|
||||||
|
// Dữ liệu tổng hợp theo tuần/tháng (nếu cần)
|
||||||
|
private List<RevenueSummaryDto> weeklySummary;
|
||||||
|
private List<RevenueSummaryDto> monthlySummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.vega.hrm.report.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class RevenueSummaryDto {
|
||||||
|
private String period; // "2025-W01" cho tuần, "2025-01" cho tháng
|
||||||
|
private Double totalRevenue;
|
||||||
|
private Double averageRevenue;
|
||||||
|
private Integer dayCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,245 @@
|
||||||
|
package com.vega.hrm.report.serivce;
|
||||||
|
|
||||||
|
import com.google.api.client.auth.oauth2.Credential;
|
||||||
|
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
|
||||||
|
import com.google.api.client.http.HttpTransport;
|
||||||
|
import com.google.api.client.json.JsonFactory;
|
||||||
|
import com.google.api.services.youtubeAnalytics.v2.YouTubeAnalytics;
|
||||||
|
import com.google.api.services.youtubeAnalytics.v2.model.QueryResponse;
|
||||||
|
import com.vega.hrm.core.component.TokenStore;
|
||||||
|
import com.vega.hrm.core.dto.GoogleOAuthConfig;
|
||||||
|
import com.vega.hrm.core.helpers.LogHelper;
|
||||||
|
import com.vega.hrm.report.response.ActivityDataDto;
|
||||||
|
import com.vega.hrm.report.response.ActivitySummaryDto;
|
||||||
|
import com.vega.hrm.report.response.ChannelActivityReportDto;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ChannelActivityReportService {
|
||||||
|
|
||||||
|
private static final String APPLICATION_NAME = "vega-report";
|
||||||
|
private final TokenStore tokenStore;
|
||||||
|
private final GoogleOAuthConfig googleOAuthConfig;
|
||||||
|
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lấy báo cáo hoạt động theo kênh từ YouTube Analytics API
|
||||||
|
*/
|
||||||
|
public ChannelActivityReportDto getChannelActivityReport(String email, String startDate, String endDate,
|
||||||
|
HttpTransport httpTransport, JsonFactory jsonFactory,
|
||||||
|
boolean includeWeekly, boolean includeMonthly)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
Credential credential = tokenStore.buildCredential(
|
||||||
|
httpTransport,
|
||||||
|
jsonFactory,
|
||||||
|
email,
|
||||||
|
googleOAuthConfig.clientId,
|
||||||
|
googleOAuthConfig.clientSecret
|
||||||
|
);
|
||||||
|
|
||||||
|
if (credential == null) {
|
||||||
|
throw new IllegalStateException("Không tìm thấy token Google cho email: " + email);
|
||||||
|
}
|
||||||
|
|
||||||
|
YouTubeAnalytics youtubeAnalytics = new YouTubeAnalytics.Builder(httpTransport, jsonFactory, credential)
|
||||||
|
.setApplicationName(APPLICATION_NAME)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Lấy các metrics: views, watchTime, subscribers, likes, comments
|
||||||
|
QueryResponse queryResponse = youtubeAnalytics.reports().query()
|
||||||
|
.setIds("channel==MINE")
|
||||||
|
.setStartDate(startDate)
|
||||||
|
.setEndDate(endDate)
|
||||||
|
.setMetrics("views,estimatedMinutesWatched,subscribersGained,likes,comments")
|
||||||
|
.setDimensions("day")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
List<ActivityDataDto> dailyActivity = parseActivityData(queryResponse);
|
||||||
|
|
||||||
|
// Tính tổng hợp
|
||||||
|
long totalViews = dailyActivity.stream().mapToLong(a -> a.getViews() != null ? a.getViews() : 0L).sum();
|
||||||
|
long totalWatchTime = dailyActivity.stream().mapToLong(a -> a.getWatchTime() != null ? a.getWatchTime() : 0L).sum();
|
||||||
|
long totalSubscribers = dailyActivity.stream().mapToLong(a -> a.getSubscribers() != null ? a.getSubscribers() : 0L).sum();
|
||||||
|
long totalLikes = dailyActivity.stream().mapToLong(a -> a.getLikes() != null ? a.getLikes() : 0L).sum();
|
||||||
|
long totalComments = dailyActivity.stream().mapToLong(a -> a.getComments() != null ? a.getComments() : 0L).sum();
|
||||||
|
|
||||||
|
int totalDays = dailyActivity.size();
|
||||||
|
double avgViews = totalDays > 0 ? (double) totalViews / totalDays : 0.0;
|
||||||
|
double avgWatchTime = totalDays > 0 ? (double) totalWatchTime / totalDays : 0.0;
|
||||||
|
double avgSubscribers = totalDays > 0 ? (double) totalSubscribers / totalDays : 0.0;
|
||||||
|
|
||||||
|
ChannelActivityReportDto.ChannelActivityReportDtoBuilder reportBuilder = ChannelActivityReportDto.builder()
|
||||||
|
.email(email)
|
||||||
|
.startDate(startDate)
|
||||||
|
.endDate(endDate)
|
||||||
|
.totalViews(totalViews)
|
||||||
|
.totalWatchTime(totalWatchTime)
|
||||||
|
.totalSubscribers(totalSubscribers)
|
||||||
|
.totalLikes(totalLikes)
|
||||||
|
.totalComments(totalComments)
|
||||||
|
.averageViews(avgViews)
|
||||||
|
.averageWatchTime(avgWatchTime)
|
||||||
|
.averageSubscribers(avgSubscribers)
|
||||||
|
.totalDays(totalDays)
|
||||||
|
.dailyActivity(dailyActivity);
|
||||||
|
|
||||||
|
// Tổng hợp theo tuần
|
||||||
|
if (includeWeekly) {
|
||||||
|
reportBuilder.weeklySummary(generateWeeklySummary(dailyActivity));
|
||||||
|
} else {
|
||||||
|
reportBuilder.weeklySummary(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tổng hợp theo tháng
|
||||||
|
if (includeMonthly) {
|
||||||
|
reportBuilder.monthlySummary(generateMonthlySummary(dailyActivity));
|
||||||
|
} else {
|
||||||
|
reportBuilder.monthlySummary(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
LogHelper.info("Đã lấy được " + dailyActivity.size() + " dòng dữ liệu hoạt động.");
|
||||||
|
return reportBuilder.build();
|
||||||
|
|
||||||
|
} catch (GoogleJsonResponseException e) {
|
||||||
|
LogHelper.error("Lỗi khi lấy dữ liệu hoạt động: " + e.getDetails().getMessage());
|
||||||
|
throw new IOException("Không thể lấy dữ liệu hoạt động: " + e.getDetails().getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse dữ liệu hoạt động từ QueryResponse
|
||||||
|
*/
|
||||||
|
private List<ActivityDataDto> parseActivityData(QueryResponse queryResponse) {
|
||||||
|
List<ActivityDataDto> result = new ArrayList<>();
|
||||||
|
|
||||||
|
if (queryResponse.getRows() != null) {
|
||||||
|
for (List<Object> row : queryResponse.getRows()) {
|
||||||
|
if (row.size() >= 6) {
|
||||||
|
String date = row.get(0).toString();
|
||||||
|
Long views = parseLong(row.get(1));
|
||||||
|
Long watchTime = parseLong(row.get(2)); // estimatedMinutesWatched (phút) -> chuyển sang giây
|
||||||
|
Long subscribers = parseLong(row.get(3));
|
||||||
|
Long likes = parseLong(row.get(4));
|
||||||
|
Long comments = parseLong(row.get(5));
|
||||||
|
|
||||||
|
// Chuyển watchTime từ phút sang giây
|
||||||
|
if (watchTime != null) {
|
||||||
|
watchTime = watchTime * 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.add(ActivityDataDto.builder()
|
||||||
|
.date(date)
|
||||||
|
.views(views)
|
||||||
|
.watchTime(watchTime)
|
||||||
|
.subscribers(subscribers)
|
||||||
|
.likes(likes)
|
||||||
|
.comments(comments)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse giá trị Long từ Object
|
||||||
|
*/
|
||||||
|
private Long parseLong(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (obj instanceof Number) {
|
||||||
|
return ((Number) obj).longValue();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Long.parseLong(obj.toString());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tổng hợp dữ liệu theo tuần
|
||||||
|
*/
|
||||||
|
private List<ActivitySummaryDto> generateWeeklySummary(List<ActivityDataDto> data) {
|
||||||
|
Map<String, List<ActivityDataDto>> weeklyGrouped = data.stream()
|
||||||
|
.collect(Collectors.groupingBy(ad -> {
|
||||||
|
LocalDate date = LocalDate.parse(ad.getDate(), DATE_FORMATTER);
|
||||||
|
int year = date.getYear();
|
||||||
|
int week = date.get(java.time.temporal.WeekFields.ISO.weekOfWeekBasedYear());
|
||||||
|
return String.format("%d-W%02d", year, week);
|
||||||
|
}));
|
||||||
|
|
||||||
|
return weeklyGrouped.entrySet().stream()
|
||||||
|
.map(entry -> {
|
||||||
|
String period = entry.getKey();
|
||||||
|
List<ActivityDataDto> weekData = entry.getValue();
|
||||||
|
|
||||||
|
long totalViews = weekData.stream().mapToLong(a -> a.getViews() != null ? a.getViews() : 0L).sum();
|
||||||
|
long totalWatchTime = weekData.stream().mapToLong(a -> a.getWatchTime() != null ? a.getWatchTime() : 0L).sum();
|
||||||
|
long totalSubscribers = weekData.stream().mapToLong(a -> a.getSubscribers() != null ? a.getSubscribers() : 0L).sum();
|
||||||
|
long totalLikes = weekData.stream().mapToLong(a -> a.getLikes() != null ? a.getLikes() : 0L).sum();
|
||||||
|
long totalComments = weekData.stream().mapToLong(a -> a.getComments() != null ? a.getComments() : 0L).sum();
|
||||||
|
|
||||||
|
return ActivitySummaryDto.builder()
|
||||||
|
.period(period)
|
||||||
|
.totalViews(totalViews)
|
||||||
|
.totalWatchTime(totalWatchTime)
|
||||||
|
.totalSubscribers(totalSubscribers)
|
||||||
|
.totalLikes(totalLikes)
|
||||||
|
.totalComments(totalComments)
|
||||||
|
.dayCount(weekData.size())
|
||||||
|
.build();
|
||||||
|
})
|
||||||
|
.sorted((a, b) -> a.getPeriod().compareTo(b.getPeriod()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tổng hợp dữ liệu theo tháng
|
||||||
|
*/
|
||||||
|
private List<ActivitySummaryDto> generateMonthlySummary(List<ActivityDataDto> data) {
|
||||||
|
Map<String, List<ActivityDataDto>> monthlyGrouped = data.stream()
|
||||||
|
.collect(Collectors.groupingBy(ad -> {
|
||||||
|
LocalDate date = LocalDate.parse(ad.getDate(), DATE_FORMATTER);
|
||||||
|
return String.format("%d-%02d", date.getYear(), date.getMonthValue());
|
||||||
|
}));
|
||||||
|
|
||||||
|
return monthlyGrouped.entrySet().stream()
|
||||||
|
.map(entry -> {
|
||||||
|
String period = entry.getKey();
|
||||||
|
List<ActivityDataDto> monthData = entry.getValue();
|
||||||
|
|
||||||
|
long totalViews = monthData.stream().mapToLong(a -> a.getViews() != null ? a.getViews() : 0L).sum();
|
||||||
|
long totalWatchTime = monthData.stream().mapToLong(a -> a.getWatchTime() != null ? a.getWatchTime() : 0L).sum();
|
||||||
|
long totalSubscribers = monthData.stream().mapToLong(a -> a.getSubscribers() != null ? a.getSubscribers() : 0L).sum();
|
||||||
|
long totalLikes = monthData.stream().mapToLong(a -> a.getLikes() != null ? a.getLikes() : 0L).sum();
|
||||||
|
long totalComments = monthData.stream().mapToLong(a -> a.getComments() != null ? a.getComments() : 0L).sum();
|
||||||
|
|
||||||
|
return ActivitySummaryDto.builder()
|
||||||
|
.period(period)
|
||||||
|
.totalViews(totalViews)
|
||||||
|
.totalWatchTime(totalWatchTime)
|
||||||
|
.totalSubscribers(totalSubscribers)
|
||||||
|
.totalLikes(totalLikes)
|
||||||
|
.totalComments(totalComments)
|
||||||
|
.dayCount(monthData.size())
|
||||||
|
.build();
|
||||||
|
})
|
||||||
|
.sorted((a, b) -> a.getPeriod().compareTo(b.getPeriod()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
package com.vega.hrm.report.serivce;
|
||||||
|
|
||||||
|
import com.vega.hrm.core.entities.RevenueData;
|
||||||
|
import com.vega.hrm.core.repositories.RevenueDataRepository;
|
||||||
|
import com.vega.hrm.report.response.ChannelRevenueReportDto;
|
||||||
|
import com.vega.hrm.report.response.RevenueDataDto;
|
||||||
|
import com.vega.hrm.report.response.RevenueSummaryDto;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ChannelRevenueReportService {
|
||||||
|
|
||||||
|
private final RevenueDataRepository revenueDataRepository;
|
||||||
|
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tạo báo cáo doanh thu theo kênh
|
||||||
|
*/
|
||||||
|
public ChannelRevenueReportDto generateRevenueReport(String email, String startDate, String endDate,
|
||||||
|
boolean includeWeekly, boolean includeMonthly) {
|
||||||
|
LocalDate start = LocalDate.parse(startDate, DATE_FORMATTER);
|
||||||
|
LocalDate end = LocalDate.parse(endDate, DATE_FORMATTER);
|
||||||
|
|
||||||
|
// Lấy tất cả dữ liệu trong khoảng thời gian
|
||||||
|
List<RevenueData> allData = revenueDataRepository.findAllByEmailAndDateRange(email, start, end);
|
||||||
|
|
||||||
|
if (allData == null || allData.isEmpty()) {
|
||||||
|
return ChannelRevenueReportDto.builder()
|
||||||
|
.email(email)
|
||||||
|
.startDate(startDate)
|
||||||
|
.endDate(endDate)
|
||||||
|
.totalRevenue(0.0)
|
||||||
|
.averageDailyRevenue(0.0)
|
||||||
|
.maxDailyRevenue(0.0)
|
||||||
|
.minDailyRevenue(0.0)
|
||||||
|
.totalDays(0)
|
||||||
|
.daysWithRevenue(0)
|
||||||
|
.dailyRevenue(new ArrayList<>())
|
||||||
|
.weeklySummary(new ArrayList<>())
|
||||||
|
.monthlySummary(new ArrayList<>())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chuyển đổi sang DTO
|
||||||
|
List<RevenueDataDto> dailyRevenue = allData.stream()
|
||||||
|
.map(this::convertToDto)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// Tính tổng hợp
|
||||||
|
List<Double> revenues = allData.stream()
|
||||||
|
.filter(r -> r.getEstimatedRevenue() != null)
|
||||||
|
.map(RevenueData::getEstimatedRevenue)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
double totalRevenue = revenues.stream().mapToDouble(Double::doubleValue).sum();
|
||||||
|
double avgRevenue = revenues.isEmpty() ? 0.0 : totalRevenue / revenues.size();
|
||||||
|
double maxRevenue = revenues.stream().mapToDouble(Double::doubleValue).max().orElse(0.0);
|
||||||
|
double minRevenue = revenues.stream().mapToDouble(Double::doubleValue).min().orElse(0.0);
|
||||||
|
|
||||||
|
int totalDays = (int) start.datesUntil(end.plusDays(1)).count();
|
||||||
|
int daysWithRevenue = revenues.size();
|
||||||
|
|
||||||
|
ChannelRevenueReportDto.ChannelRevenueReportDtoBuilder reportBuilder = ChannelRevenueReportDto.builder()
|
||||||
|
.email(email)
|
||||||
|
.startDate(startDate)
|
||||||
|
.endDate(endDate)
|
||||||
|
.totalRevenue(totalRevenue)
|
||||||
|
.averageDailyRevenue(avgRevenue)
|
||||||
|
.maxDailyRevenue(maxRevenue)
|
||||||
|
.minDailyRevenue(minRevenue)
|
||||||
|
.totalDays(totalDays)
|
||||||
|
.daysWithRevenue(daysWithRevenue)
|
||||||
|
.dailyRevenue(dailyRevenue);
|
||||||
|
|
||||||
|
// Tổng hợp theo tuần
|
||||||
|
if (includeWeekly) {
|
||||||
|
reportBuilder.weeklySummary(generateWeeklySummary(allData));
|
||||||
|
} else {
|
||||||
|
reportBuilder.weeklySummary(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tổng hợp theo tháng
|
||||||
|
if (includeMonthly) {
|
||||||
|
reportBuilder.monthlySummary(generateMonthlySummary(allData));
|
||||||
|
} else {
|
||||||
|
reportBuilder.monthlySummary(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return reportBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tổng hợp dữ liệu theo tuần
|
||||||
|
*/
|
||||||
|
private List<RevenueSummaryDto> generateWeeklySummary(List<RevenueData> data) {
|
||||||
|
Map<String, List<RevenueData>> weeklyGrouped = data.stream()
|
||||||
|
.collect(Collectors.groupingBy(rd -> {
|
||||||
|
LocalDate date = rd.getRevenueDate();
|
||||||
|
int year = date.getYear();
|
||||||
|
int week = date.get(java.time.temporal.WeekFields.ISO.weekOfWeekBasedYear());
|
||||||
|
return String.format("%d-W%02d", year, week);
|
||||||
|
}));
|
||||||
|
|
||||||
|
return weeklyGrouped.entrySet().stream()
|
||||||
|
.map(entry -> {
|
||||||
|
String period = entry.getKey();
|
||||||
|
List<RevenueData> weekData = entry.getValue();
|
||||||
|
|
||||||
|
List<Double> revenues = weekData.stream()
|
||||||
|
.filter(r -> r.getEstimatedRevenue() != null)
|
||||||
|
.map(RevenueData::getEstimatedRevenue)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
double total = revenues.stream().mapToDouble(Double::doubleValue).sum();
|
||||||
|
double avg = revenues.isEmpty() ? 0.0 : total / revenues.size();
|
||||||
|
|
||||||
|
return RevenueSummaryDto.builder()
|
||||||
|
.period(period)
|
||||||
|
.totalRevenue(total)
|
||||||
|
.averageRevenue(avg)
|
||||||
|
.dayCount(revenues.size())
|
||||||
|
.build();
|
||||||
|
})
|
||||||
|
.sorted((a, b) -> a.getPeriod().compareTo(b.getPeriod()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tổng hợp dữ liệu theo tháng
|
||||||
|
*/
|
||||||
|
private List<RevenueSummaryDto> generateMonthlySummary(List<RevenueData> data) {
|
||||||
|
Map<String, List<RevenueData>> monthlyGrouped = data.stream()
|
||||||
|
.collect(Collectors.groupingBy(rd -> {
|
||||||
|
LocalDate date = rd.getRevenueDate();
|
||||||
|
return String.format("%d-%02d", date.getYear(), date.getMonthValue());
|
||||||
|
}));
|
||||||
|
|
||||||
|
return monthlyGrouped.entrySet().stream()
|
||||||
|
.map(entry -> {
|
||||||
|
String period = entry.getKey();
|
||||||
|
List<RevenueData> monthData = entry.getValue();
|
||||||
|
|
||||||
|
List<Double> revenues = monthData.stream()
|
||||||
|
.filter(r -> r.getEstimatedRevenue() != null)
|
||||||
|
.map(RevenueData::getEstimatedRevenue)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
double total = revenues.stream().mapToDouble(Double::doubleValue).sum();
|
||||||
|
double avg = revenues.isEmpty() ? 0.0 : total / revenues.size();
|
||||||
|
|
||||||
|
return RevenueSummaryDto.builder()
|
||||||
|
.period(period)
|
||||||
|
.totalRevenue(total)
|
||||||
|
.averageRevenue(avg)
|
||||||
|
.dayCount(revenues.size())
|
||||||
|
.build();
|
||||||
|
})
|
||||||
|
.sorted((a, b) -> a.getPeriod().compareTo(b.getPeriod()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert entity sang DTO
|
||||||
|
*/
|
||||||
|
private RevenueDataDto convertToDto(RevenueData entity) {
|
||||||
|
return RevenueDataDto.builder()
|
||||||
|
.date(entity.getRevenueDate().format(DATE_FORMATTER))
|
||||||
|
.estimatedRevenue(entity.getEstimatedRevenue())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user