fix: Playwright 전메뉴 기능테스트로 발견한 화면 버그 4건 수정
- products: /api/products 가 bare 배열 반환 → PageResponse 정렬(PageHelper).
contracts/rules-commission 의 products?.list.map 크래시 + products 빈목록 동시 해결
- grade-eval: gradeEvalRules 가 PageResponse(.list) 추출하도록 수정 (rowData.map 크래시)
- batch: batch.ts 를 실제 백엔드(/settlement/run body, /jobs/{id}, /jobs)로 정렬 +
VO필드(jobId/currentStep/processedCount/finishedAt/durationSec) 어댑터 매핑
검증: Playwright 84화면 재실행 FAIL 7→3 (잔여 3건은 미구현 /api/report/*)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,14 +4,13 @@ import com.ga.api.service.product.ProductService;
|
|||||||
import com.ga.common.annotation.DataChangeLog;
|
import com.ga.common.annotation.DataChangeLog;
|
||||||
import com.ga.common.annotation.RequirePermission;
|
import com.ga.common.annotation.RequirePermission;
|
||||||
import com.ga.common.model.ApiResponse;
|
import com.ga.common.model.ApiResponse;
|
||||||
|
import com.ga.common.model.PageResponse;
|
||||||
import com.ga.core.vo.product.ProductResp;
|
import com.ga.core.vo.product.ProductResp;
|
||||||
import com.ga.core.vo.product.ProductVO;
|
import com.ga.core.vo.product.ProductVO;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Tag(name = "상품 관리")
|
@Tag(name = "상품 관리")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/products")
|
@RequestMapping("/api/products")
|
||||||
@@ -22,11 +21,13 @@ public class ProductController {
|
|||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@RequirePermission(menu = "PRODUCT", perm = "READ")
|
@RequirePermission(menu = "PRODUCT", perm = "READ")
|
||||||
public ApiResponse<List<ProductResp>> list(@RequestParam(required = false) Long companyId,
|
public ApiResponse<PageResponse<ProductResp>> list(@RequestParam(defaultValue = "1") int pageNum,
|
||||||
|
@RequestParam(defaultValue = "10") int pageSize,
|
||||||
|
@RequestParam(required = false) Long companyId,
|
||||||
@RequestParam(required = false) String insuranceType,
|
@RequestParam(required = false) String insuranceType,
|
||||||
@RequestParam(required = false) String keyword,
|
@RequestParam(required = false) String keyword,
|
||||||
@RequestParam(required = false) String isActive) {
|
@RequestParam(required = false) String isActive) {
|
||||||
return ApiResponse.ok(productService.list(companyId, insuranceType, keyword, isActive));
|
return ApiResponse.ok(productService.list(pageNum, pageSize, companyId, insuranceType, keyword, isActive));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{productId}")
|
@GetMapping("/{productId}")
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ package com.ga.api.service.product;
|
|||||||
|
|
||||||
import com.ga.common.exception.BizException;
|
import com.ga.common.exception.BizException;
|
||||||
import com.ga.common.exception.ErrorCode;
|
import com.ga.common.exception.ErrorCode;
|
||||||
|
import com.ga.common.model.PageResponse;
|
||||||
import com.ga.core.mapper.product.ProductMapper;
|
import com.ga.core.mapper.product.ProductMapper;
|
||||||
import com.ga.core.vo.product.ProductResp;
|
import com.ga.core.vo.product.ProductResp;
|
||||||
import com.ga.core.vo.product.ProductVO;
|
import com.ga.core.vo.product.ProductVO;
|
||||||
|
import com.github.pagehelper.PageHelper;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class ProductService {
|
public class ProductService {
|
||||||
@@ -18,8 +18,9 @@ public class ProductService {
|
|||||||
private final ProductMapper mapper;
|
private final ProductMapper mapper;
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public List<ProductResp> list(Long companyId, String insuranceType, String keyword, String isActive) {
|
public PageResponse<ProductResp> list(int pageNum, int pageSize, Long companyId, String insuranceType, String keyword, String isActive) {
|
||||||
return mapper.selectList(companyId, insuranceType, keyword, isActive);
|
PageHelper.startPage(pageNum, pageSize);
|
||||||
|
return PageResponse.of(mapper.selectList(companyId, insuranceType, keyword, isActive));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
|
|||||||
@@ -29,19 +29,60 @@ export interface BatchHistoryResp {
|
|||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 백엔드 batch_job_log VO (실제 응답 형태) */
|
||||||
|
interface BatchJobVO {
|
||||||
|
jobId: number;
|
||||||
|
jobName: string;
|
||||||
|
status: string; // REQUESTED | STARTED | COMPLETED | FAILED
|
||||||
|
currentStep?: string;
|
||||||
|
totalCount?: number;
|
||||||
|
processedCount?: number;
|
||||||
|
errorCount?: number;
|
||||||
|
errorMessage?: string;
|
||||||
|
startedAt?: number[] | string;
|
||||||
|
finishedAt?: number[] | string;
|
||||||
|
durationSec?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** LocalDateTime 배열([y,M,d,h,m,s,..]) → 'YYYY-MM-DD HH:mm:ss' */
|
||||||
|
function fmtTs(v?: number[] | string): string | undefined {
|
||||||
|
if (v == null) return undefined;
|
||||||
|
if (typeof v === 'string') return v;
|
||||||
|
const [y, mo, d, h = 0, mi = 0, s = 0] = v;
|
||||||
|
const p = (n: number) => String(n).padStart(2, '0');
|
||||||
|
return `${y}-${p(mo)}-${p(d)} ${p(h)}:${p(mi)}:${p(s)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStatus(s: string): BatchJobLogResp['status'] {
|
||||||
|
if (s === 'STARTED') return 'RUNNING';
|
||||||
|
if (s === 'REQUESTED' || s === 'RUNNING' || s === 'COMPLETED' || s === 'FAILED') return s;
|
||||||
|
return 'RUNNING';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 백엔드 VO → 프론트 BatchJobLogResp 어댑터 */
|
||||||
|
function toJobLog(v: BatchJobVO): BatchJobLogResp {
|
||||||
|
return {
|
||||||
|
jobLogId: v.jobId,
|
||||||
|
jobName: v.jobName,
|
||||||
|
status: mapStatus(v.status),
|
||||||
|
stepName: v.currentStep,
|
||||||
|
totalCount: v.totalCount,
|
||||||
|
successCount: v.processedCount,
|
||||||
|
errorCount: v.errorCount,
|
||||||
|
startedAt: fmtTs(v.startedAt),
|
||||||
|
endedAt: fmtTs(v.finishedAt),
|
||||||
|
durationMs: v.durationSec != null ? v.durationSec * 1000 : undefined,
|
||||||
|
errorMessage: v.errorMessage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const batchRunApi = {
|
export const batchRunApi = {
|
||||||
run: (settleMonth: string, companyCode?: string) =>
|
run: (settleMonth: string, companyCode?: string) =>
|
||||||
unwrap<number>(
|
unwrap<number>(api.post('/api/batch/settlement/run', { settleMonth, companyCode })),
|
||||||
api.post('/api/batch/settlement/run', null, {
|
|
||||||
params: { settleMonth, companyCode },
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
status: (jobLogId: number) =>
|
status: (jobLogId: number) =>
|
||||||
unwrap<BatchJobLogResp>(
|
unwrap<BatchJobVO>(api.get(`/api/batch/jobs/${jobLogId}`)).then(toJobLog),
|
||||||
api.get('/api/batch/settlement/status', { params: { jobLogId } }),
|
|
||||||
),
|
|
||||||
history: (size = 10) =>
|
history: (size = 10) =>
|
||||||
unwrap<BatchHistoryResp[]>(
|
unwrap<BatchJobVO[]>(api.get('/api/batch/jobs')).then((rows) =>
|
||||||
api.get('/api/batch/settlement/history', { params: { size } }),
|
rows.slice(0, size).map(toJobLog) as BatchHistoryResp[],
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -153,9 +153,9 @@ export const operationApi = {
|
|||||||
hqSettleDelete: (id: number) =>
|
hqSettleDelete: (id: number) =>
|
||||||
unwrap<void>(api.delete(`/api/hq-settles/${id}`)),
|
unwrap<void>(api.delete(`/api/hq-settles/${id}`)),
|
||||||
|
|
||||||
// GradeEval Rules
|
// GradeEval Rules — 백엔드는 PageResponse({list}) 반환이므로 .list 추출
|
||||||
gradeEvalRules: () =>
|
gradeEvalRules: () =>
|
||||||
unwrap<GradeEvalRuleRow[]>(api.get('/api/grade-evaluations/rules')),
|
unwrap<{ list: GradeEvalRuleRow[] }>(api.get('/api/grade-evaluations/rules')).then((r) => r.list ?? []),
|
||||||
gradeEvalRuleGet: (id: number) =>
|
gradeEvalRuleGet: (id: number) =>
|
||||||
unwrap<GradeEvalRuleRow>(api.get(`/api/grade-evaluations/rules/${id}`)),
|
unwrap<GradeEvalRuleRow>(api.get(`/api/grade-evaluations/rules/${id}`)),
|
||||||
gradeEvalRuleCreate: (body: Partial<GradeEvalRuleRow>) =>
|
gradeEvalRuleCreate: (body: Partial<GradeEvalRuleRow>) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user