Files
sam-docs/dev/guides/performance-report-excel-export.md
김보곤 007e8a3ed3 docs: [quality] 실적신고 엑셀 Export 구현 가이드 추가
- dev/guides/performance-report-excel-export.md 신규 작성
- 품질관리 README 미구현 항목 → 구현 완료로 업데이트
- INDEX.md에 새 문서 등록
2026-03-17 16:55:19 +09:00

240 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 실적신고 확정건 엑셀 Export 구현 가이드
> **작성일**: 2026-03-17
> **상태**: 운영 중
---
## 1. 개요
### 1.1 목적
품질관리 > 실적신고관리에서 **확정건 엑셀다운로드** 버튼 클릭 시, 건기원 제출 양식(품질인정자재등의 판매실적 대장)에 맞는 xlsx 파일을 생성하여 다운로드한다.
### 1.2 왜 PhpSpreadsheet 직접 사용인가
기존 `ExportService`(Maatwebsite/Excel)는 단순 테이블 Export에 적합하지만, 건기원 양식은 다음 요구사항이 있어 PhpSpreadsheet를 직접 사용한다:
- 다중 헤더 행 (제목, 부제목, 카테고리 헤더, 컬럼 헤더)
- 복잡한 셀 병합 (같은 품질관리서의 여러 개소 → 문서 수준 컬럼 병합)
- 카테고리별 배경색 (5개 카테고리, 각기 다른 색상)
- 회사 정보 섹션 (Tenant 모델 참조)
- 27개 컬럼 (A~AA)
---
## 2. 아키텍처
### 2.1 구성 요소
```
┌─────────────────────────────────────────────────────────┐
│ React (PerformanceReportList.tsx) │
│ └── handleExcelDownload() │
│ └── exportConfirmedExcel() [Server Action] │
│ └── fetch(API, Accept: xlsx) │
└────────────────────┬────────────────────────────────────┘
│ GET /api/v1/quality/performance-reports/export-excel
┌─────────────────────────────────────────────────────────┐
│ API (PerformanceReportController) │
│ └── exportExcel() │
│ └── PerformanceReportService.exportConfirmed() │
│ └── PerformanceReportExcelService.generate() │
│ ├── getConfirmedReports() │
│ ├── setColumnWidths() │
│ ├── writeTitle() │
│ ├── writeCompanyInfo() │
│ ├── writeCategoryHeaders() │
│ ├── writeColumnHeaders() │
│ └── writeDataRows() + merge logic │
│ └── StreamedResponse (xlsx) │
└─────────────────────────────────────────────────────────┘
```
### 2.2 파일 구조
| 프로젝트 | 파일 | 역할 |
|---------|------|------|
| API | `app/Services/PerformanceReportExcelService.php` | 엑셀 생성 전담 서비스 |
| API | `app/Services/PerformanceReportService.php` | `exportConfirmed()` 메서드 |
| API | `app/Http/Controllers/Api/V1/PerformanceReportController.php` | `exportExcel()` 액션 |
| API | `routes/api/v1/quality.php` | GET 라우트 |
| React | `components/quality/PerformanceReportManagement/actions.ts` | `exportConfirmedExcel()` 서버 액션 |
| React | `components/quality/PerformanceReportManagement/PerformanceReportList.tsx` | `handleExcelDownload()` |
---
## 3. 엑셀 양식 구조
### 3.1 시트 레이아웃
```
Row 1 : [A1:AA1 병합] 제목 — "품질인정자재등의 판매실적 제출서식" (돋움 24pt bold)
Row 2 : 빈 행
Row 3 : [A3:AA3 병합] 부제목 — "품질인정자재등의 판매실적 대장(2026년 1분기)" (돋움 18pt)
Row 4 : 빈 행
Row 5~9 : 회사 정보 (Tenant 모델)
Row 10 : 빈 행
Row 11 : 카테고리 헤더 (5개 카테고리, 각각 배경색)
Row 12 : 컬럼 헤더 (27개)
Row 13+ : 데이터 행
```
### 3.2 카테고리 헤더 (Row 11)
| 범위 | 카테고리 | 배경색 |
|------|---------|--------|
| A~L | 건축자재내역 | `#DAEEF3` (연한 파랑) |
| M~O | 건축공사장 | `#E2EFDA` (연한 초록) |
| P~S | 공사감리자 | `#FCE4D6` (연한 주황) |
| T~W | 공사시공자 | `#EDEDED` (연한 회색) |
| X~AA | 자재유통업자 | `#FFF2CC` (연한 노랑) |
### 3.3 컬럼 헤더 (Row 12) — 27개
| 컬럼 | 헤더 | 데이터 소스 | 병합 |
|------|------|-----------|------|
| A | 일련번호 | 순번 (auto) | O |
| B | 품질관리서번호 | `quality_doc_number` | O |
| C | 작성일 | `received_date` | O |
| D | 인정품목 | **미확정** → '' | O |
| E | 규격(품명) | `orderItem.item_name` | O |
| F | 규격(종류) | `orderItem.specification` | O |
| G | 제품검사일 | `options.inspection.end_date` | O |
| H | 내화성능시간 | **미확정** → '' | O |
| I | 사용부위 | **미확정** → '' | O |
| J | 로트번호 | **미확정** → '' | X |
| K | 규격(치수) | `post_width × post_height` | X |
| L | 수량 | `orderItem.quantity` | X |
| M | 공사명칭 | `options.construction_site.name` | O |
| N | 소재지 | `options.construction_site.land_location` | O |
| O | 번지 | `options.construction_site.lot_number` | O |
| P | 사무소명 | `options.supervisor.office` | O |
| Q | 사무소주소 | `options.supervisor.address` | O |
| R | 성명 | `options.supervisor.name` | O |
| S | 연락처 | `options.supervisor.phone` | O |
| T | 업체명 | `options.contractor.company` | O |
| U | 업체주소 | `options.contractor.address` | O |
| V | 성명 | `options.contractor.name` | O |
| W | 연락처 | `options.contractor.phone` | O |
| X | 업체명 | `options.material_distributor.company` | O |
| Y | 업체주소 | `options.material_distributor.address` | O |
| Z | 대표자명 | `options.material_distributor.ceo` | O |
| AA | 연락처 | `options.material_distributor.phone` | O |
> **병합 O**: 같은 품질관리서의 여러 개소 → 첫 행에만 기록, 나머지 행 병합
> **병합 X**: 개소별로 다른 데이터 → 매 행마다 기록
### 3.4 셀 병합 로직
하나의 `PerformanceReport``QualityDocument` → 여러 `QualityDocumentLocation`
```php
// 같은 품질관리서에 개소가 3개인 경우:
// Row 13: 일련번호=1, 품관번호, 작성일, ... (문서 데이터) + 개소1 데이터
// Row 14: + 개소2 데이터
// Row 15: + 개소3 데이터
// → A13:A15, B13:B15, C13:C15, ... 병합 (J, K, L 제외)
```
---
## 4. 미확정 필드 (추후 확장 포인트)
4개 필드가 현재 빈 문자열을 반환하며, 별도 메서드로 분리되어 있어 추후 데이터 매핑만 추가하면 된다:
| 메서드 | 컬럼 | 설명 |
|--------|------|------|
| `getProductCategory($location)` | D | 인정품목 |
| `getFireResistanceTime($location)` | H | 내화성능시간 |
| `getUsagePart($location)` | I | 사용부위 |
| `getLotNumber($location)` | J | 로트번호 |
> 이 4개 필드의 데이터 소스가 확정되면 해당 메서드 내부만 수정하면 된다.
> 참고: `project_excel_export_pending_fields.md` (메모리)
---
## 5. API 엔드포인트
```
GET /api/v1/quality/performance-reports/export-excel
```
| 파라미터 | 타입 | 필수 | 설명 |
|---------|------|------|------|
| `year` | int | O | 연도 (기본: 현재 연도) |
| `quarter` | int | O | 분기 (기본: 현재 분기) |
**응답**: `StreamedResponse` (xlsx 파일)
```
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Content-Disposition: attachment; filename*=UTF-8''{회사명}_품질인정자재등의_판매실적_대장_{year}년_{quarter}분기.xlsx
```
---
## 6. 프론트엔드 패턴
### 6.1 Server Action (Blob 다운로드)
기존 급여관리 `exportPayrollExcel` 패턴을 재사용:
```typescript
// actions.ts
export async function exportConfirmedExcel(params) {
const cookieStore = await cookies();
const token = cookieStore.get('access_token')?.value;
const response = await fetch(url, {
headers: {
Accept: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
Authorization: `Bearer ${token}`,
'X-API-KEY': process.env.API_KEY,
},
});
const blob = await response.blob();
return { success: true, data: blob, filename };
}
```
### 6.2 컴포넌트 (다운로드 트리거)
```typescript
// PerformanceReportList.tsx
const handleExcelDownload = useCallback(async () => {
const result = await exportConfirmedExcel({ year, quarter });
if (result.success && result.data) {
const url = URL.createObjectURL(result.data);
const a = document.createElement('a');
a.href = url;
a.download = result.filename;
a.click();
URL.revokeObjectURL(url);
}
}, [year, quarter]);
```
---
## 7. 의존성
| 패키지 | 버전 | 프로젝트 | 용도 |
|--------|------|---------|------|
| `phpoffice/phpspreadsheet` | ^1.30 | API | xlsx 생성 |
> `--ignore-platform-reqs`로 설치됨 (Docker 컨테이너에 `ext-gd` 미설치). xlsx 생성에는 gd 불필요.
> 개발 서버에는 gd 확장이 설치되어 있어 문제 없음.
---
## 관련 문서
- [품질관리 시스템](../../features/quality-management/README.md)
- [API 개발 규칙](../standards/api-rules.md)
---
**최종 업데이트**: 2026-03-17