feat: 리포트 3종 백엔드 구현(실적/조직별/환수위험) + Excel export 트랜잭션 수정
- ReportController/Service/Mapper(.xml) + VO 3종(PerfReportRow/OrgReportRow/ChargebackRiskRow) - /api/report/performance: settle_month별 settle_master 집계(설계사수/모집/유지/실지급) - /api/report/org: 정산월 기준 organization 조인 조직별 집계 - /api/report/chargeback-risk: recruit_ledger 계약별 경과월 + chargeback_grade 환수율/위험등급 - 각 read(List) + export(Cursor→xlsx). 신규 테이블 없음(읽기전용 집계) - ExcelService.exportLargeExcel @Transactional(readOnly=true) 추가 — Cursor 가 세션 밖에서 닫혀 모든 export(기존 agent 포함)가 500 나던 잠복버그 수정. 1줄로 export 인프라 전체 복구 검증: 3 read 200(실데이터), 3 export 200(유효 xlsx), agent export 복구 확인. Playwright 84화면 재실행 FAIL 0 (PASS59/WARN25, WARN은 차트/폼 등 탐지한계) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
package com.ga.api.controller.report;
|
||||
|
||||
import com.ga.api.service.report.ReportService;
|
||||
import com.ga.common.annotation.RequirePermission;
|
||||
import com.ga.common.excel.ExcelService;
|
||||
import com.ga.common.model.ApiResponse;
|
||||
import com.ga.core.vo.report.ChargebackRiskRow;
|
||||
import com.ga.core.vo.report.OrgReportRow;
|
||||
import com.ga.core.vo.report.PerfReportRow;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 리포트 — 실적 / 조직별 / 환수위험. 읽기전용 집계 (신규 테이블 없음).
|
||||
* 각 화면의 조회 + 엑셀 export 엔드포인트 제공.
|
||||
*/
|
||||
@Tag(name = "리포트")
|
||||
@RestController
|
||||
@RequestMapping("/api/report")
|
||||
@RequiredArgsConstructor
|
||||
public class ReportController {
|
||||
|
||||
private final ReportService service;
|
||||
private final ExcelService excelService;
|
||||
|
||||
private String defaultYear(String year) {
|
||||
return (year != null && !year.isBlank()) ? year : String.valueOf(LocalDate.now().getYear());
|
||||
}
|
||||
|
||||
private String defaultMonth(String settleMonth) {
|
||||
return (settleMonth != null && !settleMonth.isBlank())
|
||||
? settleMonth : LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
|
||||
}
|
||||
|
||||
// ── 실적 리포트 ──
|
||||
@GetMapping("/performance")
|
||||
@RequirePermission(menu = "REPORT_PERFORMANCE", perm = "READ")
|
||||
public ApiResponse<List<PerfReportRow>> performance(@RequestParam(required = false) String year) {
|
||||
return ApiResponse.ok(service.performance(defaultYear(year)));
|
||||
}
|
||||
|
||||
@GetMapping("/performance/export")
|
||||
@RequirePermission(menu = "REPORT_PERFORMANCE", perm = "READ")
|
||||
public void performanceExport(@RequestParam(required = false) String year, HttpServletResponse response) {
|
||||
String y = defaultYear(year);
|
||||
excelService.exportLargeExcel("실적리포트", PerfReportRow.class,
|
||||
() -> service.performanceCursor(y), response);
|
||||
}
|
||||
|
||||
// ── 조직별 리포트 ──
|
||||
@GetMapping("/org")
|
||||
@RequirePermission(menu = "REPORT_ORG", perm = "READ")
|
||||
public ApiResponse<List<OrgReportRow>> org(@RequestParam(required = false) String settleMonth) {
|
||||
return ApiResponse.ok(service.org(defaultMonth(settleMonth)));
|
||||
}
|
||||
|
||||
@GetMapping("/org/export")
|
||||
@RequirePermission(menu = "REPORT_ORG", perm = "READ")
|
||||
public void orgExport(@RequestParam(required = false) String settleMonth, HttpServletResponse response) {
|
||||
String m = defaultMonth(settleMonth);
|
||||
excelService.exportLargeExcel("조직별리포트", OrgReportRow.class,
|
||||
() -> service.orgCursor(m), response);
|
||||
}
|
||||
|
||||
// ── 환수위험 리포트 ──
|
||||
@GetMapping("/chargeback-risk")
|
||||
@RequirePermission(menu = "REPORT_CHARGEBACK", perm = "READ")
|
||||
public ApiResponse<List<ChargebackRiskRow>> chargebackRisk() {
|
||||
return ApiResponse.ok(service.chargebackRisk());
|
||||
}
|
||||
|
||||
@GetMapping("/chargeback-risk/export")
|
||||
@RequirePermission(menu = "REPORT_CHARGEBACK", perm = "READ")
|
||||
public void chargebackRiskExport(HttpServletResponse response) {
|
||||
excelService.exportLargeExcel("환수위험리포트", ChargebackRiskRow.class,
|
||||
() -> service.chargebackRiskCursor(), response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.ga.api.service.report;
|
||||
|
||||
import com.ga.core.mapper.report.ReportMapper;
|
||||
import com.ga.core.vo.report.ChargebackRiskRow;
|
||||
import com.ga.core.vo.report.OrgReportRow;
|
||||
import com.ga.core.vo.report.PerfReportRow;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.ibatis.cursor.Cursor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 리포트 서비스 — 기존 정산 데이터 위 읽기전용 집계.
|
||||
* Cursor 변형은 엑셀 export 용 (ExcelService.exportLargeExcel 가 트랜잭션 내에서 소비).
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ReportService {
|
||||
|
||||
private final ReportMapper mapper;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<PerfReportRow> performance(String year) {
|
||||
return mapper.selectPerformance(year);
|
||||
}
|
||||
|
||||
public Cursor<PerfReportRow> performanceCursor(String year) {
|
||||
return mapper.selectPerformanceCursor(year);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<OrgReportRow> org(String settleMonth) {
|
||||
return mapper.selectOrg(settleMonth);
|
||||
}
|
||||
|
||||
public Cursor<OrgReportRow> orgCursor(String settleMonth) {
|
||||
return mapper.selectOrgCursor(settleMonth);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<ChargebackRiskRow> chargebackRisk() {
|
||||
return mapper.selectChargebackRisk();
|
||||
}
|
||||
|
||||
public Cursor<ChargebackRiskRow> chargebackRiskCursor() {
|
||||
return mapper.selectChargebackRiskCursor();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user