# 손익계산서 API 명세 (서비스 이관) > **작성일**: 2026-03-20 > **상태**: API 완료, React 구현 대기 > **대상**: React 프론트엔드 개발자 --- ## 1. 개요 회계관리 메뉴에 **손익계산서** 화면을 구현한다. 기간별 당기/전기 비교와 월별 비교 두 가지 보기 모드를 제공한다. 한국 일반기업회계기준에 따른 10개 항목(I~X) 구조로 매출액, 매출원가, 영업이익, 당기순이익 등을 표시한다. ### 메뉴 위치 ``` 회계관리 ├── 일반전표입력 ← 기존 ├── 계정별원장 ← 기존 ├── 손익계산서 ← 이 화면 ├── ... ``` --- ## 2. API 엔드포인트 ### 2.1 기간별 손익계산서 ``` GET /api/v1/income-statement ``` **요청 파라미터** (Query String): | 파라미터 | 타입 | 필수 | 설명 | 예시 | |---------|------|:----:|------|------| | `start_date` | `string(date)` | Y | 당기 시작일 | `2026-01-01` | | `end_date` | `string(date)` | Y | 당기 종료일 | `2026-12-31` | | `unit` | `string` | N | `won`(기본) / `thousand` / `million` | `won` | > **전기**: API가 자동으로 전년 동기를 계산 (예: 2026 → 2025) **응답 예시**: ```json { "success": true, "message": "조회되었습니다.", "data": { "period": { "current": { "start": "2026-01-01", "end": "2026-12-31", "label": "제 2 (당)기" }, "previous": { "start": "2025-01-01", "end": "2025-12-31", "label": "제 1 (전)기" } }, "unit": "won", "sections": [ { "code": "I", "name": "매출액", "current_amount": 50000000, "previous_amount": 30000000, "items": [ { "code": "40400", "name": "용역매출", "current": 50000000, "previous": 30000000 } ], "is_calculated": false }, { "code": "II", "name": "매출원가", "current_amount": 20000000, "previous_amount": 15000000, "items": [ { "code": "50100", "name": "서비스매출원가", "current": 12000000, "previous": 8000000 }, { "code": "50200", "name": "외주용역비", "current": 8000000, "previous": 7000000 } ], "is_calculated": false }, { "code": "III", "name": "매출총이익", "current_amount": 30000000, "previous_amount": 15000000, "items": [], "is_calculated": true } ] } } ``` --- ### 2.2 월별 손익계산서 ``` GET /api/v1/income-statement/monthly ``` **요청 파라미터** (Query String): | 파라미터 | 타입 | 필수 | 설명 | 예시 | |---------|------|:----:|------|------| | `year` | `integer` | Y | 조회 연도 | `2026` | | `unit` | `string` | N | `won`(기본) / `thousand` / `million` | `won` | **응답 예시**: ```json { "success": true, "message": "조회되었습니다.", "data": { "year": 2026, "fiscal_year": 2, "fiscal_label": "제 2 기", "unit": "won", "months": [ { "month": "01", "label": "1월", "sections": [ { "code": "I", "name": "매출액", "current_amount": 5000000, "previous_amount": 5000000, "items": [ { "code": "40400", "name": "용역매출", "current": 5000000, "previous": 5000000 } ], "is_calculated": false } ] }, { "month": "02", "label": "2월", "sections": [] } ] } } ``` > **참고**: 미래 월은 응답에 포함되지 않는다. > 월별 데이터에서 `previous_amount`와 `item.previous`는 `current`와 동일한 값 (월별 비교에서는 의미 없음). --- ## 3. TypeScript 인터페이스 ```typescript // 기간별 손익계산서 응답 interface IncomeStatementResponse { success: boolean; message: string; data: { period: { current: { start: string; end: string; label: string }; // "제 2 (당)기" previous: { start: string; end: string; label: string }; // "제 1 (전)기" }; unit: 'won' | 'thousand' | 'million'; sections: PLSection[]; }; } // 월별 손익계산서 응답 interface MonthlyIncomeStatementResponse { success: boolean; message: string; data: { year: number; fiscal_year: number; fiscal_label: string; // "제 2 기" unit: 'won' | 'thousand' | 'million'; months: MonthPL[]; }; } interface MonthPL { month: string; // "01", "02", ... label: string; // "1월", "2월", ... sections: PLSection[]; } interface PLSection { code: string; // "I", "II", "III", ... "X" name: string; // "매출액", "매출원가", "매출총이익", ... current_amount: number; // 당기 금액 previous_amount: number; // 전기 금액 items: PLItem[]; // 세부 계정과목 (계산 항목은 빈 배열) is_calculated: boolean; // true = 계산 항목 (III, V, VIII, X) } interface PLItem { code: string; // 계정코드 "40400" name: string; // 계정명 "용역매출" current: number; // 당기 금액 previous: number; // 전기 금액 } ``` --- ## 4. 화면 구현 가이드 ### 4.1 화면 구조 ``` ┌───────────────────────────────────────────────────┐ │ 📊 손익계산서 [인쇄] │ ├───────────────────────────────────────────────────┤ │ [기간 보기] [월별 보기] [당기만] [당기+전기] │ │ 기간: [시작일]~[종료일] 단위: [원▾] [조회] │ ├───────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────┐ │ │ │ 과 목 │ 제 2 (당)기 │ 제 1 (전)기│ │ │ │ │ 금 액 │ 금 액 │ │ │ ├─────────────────┼──────────────┼─────────────┤ │ │ │ I. 매출액 │ │ │ │ │ │ 용역매출 │ 50,000,000 │ │ │ │ │ │ │ 50,000,000 │ │ │ ├─────────────────┼──────────────┼─────────────┤ │ │ │ II. 매출원가 │ │ │ │ │ │ 서비스매출원가│ 12,000,000 │ │ │ │ │ 외주용역비 │ 8,000,000 │ │ │ │ │ │ │ 20,000,000 │ │ │ ├─────────────────┼──────────────┼─────────────┤ │ │ │ III. 매출총이익 │ │ 30,000,000 │ │ ← 강조 (연두) │ │ ... │ │ │ │ │ │ X. 당기순이익 │ │ 25,000,000 │ │ ← 강조 (연두) │ └─────────────────┴──────────────┴─────────────┘ │ │ (단위: 원) │ └───────────────────────────────────────────────────┘ ``` ### 4.2 보기 모드 전환 | 모드 | 필터 | API | 설명 | |------|------|-----|------| | **기간 보기** | 시작일~종료일 + 단위 | `GET /income-statement` | 당기+전기 2열 비교 | | **월별 보기** | 연도 + 단위 | `GET /income-statement/monthly` | 월별 비교 | - 모드 전환 시 기존 데이터 초기화 후 새로 조회 - 기본: 기간 보기, 당해 1/1 ~ 12/31 ### 4.3 기간 보기 테이블 (PeriodTable) **컬럼 구성**: | 상태 | 컬럼 | |------|------| | `당기만` | 과목 \| 금액(세부) \| 금액(소계) | | `당기+전기` | 과목 \| 당기(세부) \| 당기(소계) \| 전기(세부) \| 전기(소계) | **행 유형**: | 행 유형 | 조건 | 스타일 | 금액 표시 | |---------|------|--------|----------| | **섹션 헤더** | `is_calculated === false` | 굵은 글씨 | items 없으면 소계열에 금액, 있으면 빈칸 | | **세부 항목** | `items[]` | 들여쓰기(pl-12), 일반 | 세부열에 금액, 마지막 항목의 소계열에 섹션 합계 | | **계산 항목** | `is_calculated === true` | 강조 배경 | 소계열에만 금액 | **강조 표시 (emerald-50 배경)**: - III (매출총이익), V (영업이익), VIII (법인세비용차감전순이익), X (당기순이익) ### 4.4 월별 보기 테이블 (MonthlyTable) **단일 월 선택**: 기간 보기와 유사한 1열 테이블 **전체 월 선택**: 가로 스크롤 비교 테이블 ``` ┌──────────────┬──────┬──────┬──────┬─────┐ │ 과목 (sticky)│ 1월 │ 2월 │ 3월 │ ... │ ├──────────────┼──────┼──────┼──────┼─────┤ │ I. 매출액 │ 5M │ 4M │ 6M │ ... │ │ 용역매출 │ 5M │ 4M │ 6M │ ... │ │ II. 매출원가 │ 2M │ 1M │ 3M │ ... │ │ III. 매출총이│ 3M │ 3M │ 3M │ ... │ ← 강조 │ ... │ │ │ │ │ └──────────────┴──────┴──────┴──────┴─────┘ ``` - 과목 열: `sticky left-0` (가로 스크롤 시 고정) - 최소 너비: `months.length * 110 + 200` px ### 4.5 월 선택 버튼 (월별 보기) ``` [전체] [1월] [2월] [3월] ... [12월] ``` - 데이터가 있는 월만 버튼 표시 (미래 월은 API 응답에 없음) - `전체` 선택 시 가로 스크롤 비교 테이블 - 개별 월 선택 시 단일 열 테이블 ### 4.6 숫자 포맷 ```typescript const fmt = (n: number): string => { if (n === 0 || n === null || n === undefined) return ''; if (n < 0) return '(' + Math.abs(n).toLocaleString() + ')'; return n.toLocaleString(); }; const UNIT_LABELS: Record = { won: '원', thousand: '천원', million: '백만원' }; ``` ### 4.7 인쇄 - `window.print()` 사용 - 조회 조건, 인쇄 버튼에 `no-print` 클래스 - CSS: `@media print { .no-print { display: none !important; } }` ### 4.8 기본값 | 항목 | 기본값 | |------|--------| | 보기 모드 | 기간 보기 | | 시작일 | 당해 1/1 | | 종료일 | 당해 12/31 | | 단위 | 원 (`won`) | | 당기/전기 | 당기만 | | 월 선택 | 전체 | - 페이지 진입 시 기본값으로 자동 조회 --- ## 5. 손익계산서 항목 구조 ``` I. 매출액 ← 합계 항목 (세부 계정과목 표시) II. 매출원가 ← 합계 항목 III. 매출총이익 = I - II ← 계산 항목 (is_calculated: true, 강조) IV. 판매비와관리비 ← 합계 항목 V. 영업이익 = III - IV ← 계산 항목 (강조) VI. 영업외수익 ← 합계 항목 VII. 영업외비용 ← 합계 항목 VIII.법인세비용차감전순이익 ← 계산 항목 IX. 법인세비용 ← 합계 항목 X. 당기순이익 = VIII - IX ← 계산 항목 (강조) ``` - **합계 항목**: `items[]`에 세부 계정과목 포함, `current_amount`/`previous_amount`는 합계 - **계산 항목**: `items[]`는 빈 배열, `is_calculated: true`, 금액은 공식으로 계산된 결과 --- ## 6. MNG 참고 화면 > MNG 개발서버: `https://admin.codebridge-x.com` → 회계/세무관리 > 손익계산서 | 파일 | 설명 | |------|------| | `mng/app/Http/Controllers/Finance/IncomeStatementController.php` | 전체 로직 (PL_STRUCTURE, buildSections) | | `mng/resources/views/finance/income-statement.blade.php` | React 컴포넌트 (PeriodTable, MonthlyTable) | --- ## 관련 문서 - [dev/dev_plans/income-statement-service-migration-plan.md](../../dev/dev_plans/income-statement-service-migration-plan.md) — 이관 기획서 - [frontend/api-specs/account-ledger-service-api.md](account-ledger-service-api.md) — 계정별원장 API 명세 - [features/finance/README.md](../../features/finance/README.md) — 재무관리 기능 개요 --- **최종 업데이트**: 2026-03-20