# 계정별원장 API 명세 (서비스 이관)

> **작성일**: 2026-03-20
> **상태**: API 완료, React 구현 대기
> **대상**: React 프론트엔드 개발자

---

## 1. 개요

회계관리 메뉴에 **계정별원장** 화면을 구현한다.
선택한 계정과목의 기간별 거래 내역을 조회하고, 월별 소계/누계/잔액을 표시한다.
카드거래인 경우 카드사·가맹점 정보를 추가로 표시하고, 행 클릭 시 전표 상세를 모달로 보여준다.

### 메뉴 위치

```
회계관리
├── 일반전표입력       ← 기존
├── 계정별원장         ← 이 화면
├── 손익계산서         ← 기존
├── ...
```

---

## 2. API 엔드포인트

### 2.1 계정별원장 조회

```
GET /api/v1/account-ledger
```

**요청 파라미터** (Query String):

| 파라미터 | 타입 | 필수 | 설명 | 예시 |
|---------|------|:----:|------|------|
| `start_date` | `string(date)` | Y | 조회 시작일 | `2026-01-01` |
| `end_date` | `string(date)` | Y | 조회 종료일 | `2026-03-20` |
| `account_code` | `string` | Y | 계정과목 코드 | `81100` |

**응답 예시**:

```json
{
  "success": true,
  "message": "조회되었습니다.",
  "data": {
    "account": {
      "code": "81100",
      "name": "복리후생비",
      "category": "expense"
    },
    "period": {
      "start_date": "2026-01-01",
      "end_date": "2026-03-20"
    },
    "carry_forward": {
      "debit": 0,
      "credit": 0,
      "balance": 0
    },
    "monthly_data": [
      {
        "month": "2026-01",
        "items": [
          {
            "date": "2026-01-11",
            "description": "복리후생비",
            "trading_partner_name": "스타벅스 강남점",
            "biz_no": "1234567890",
            "debit_amount": 160000,
            "credit_amount": 0,
            "balance": 160000,
            "source_type": "ecard_transaction",
            "source_id": 45,
            "card_tx": {
              "card_num": "9411320012345678",
              "card_company_name": "삼성카드",
              "merchant_name": "스타벅스 강남점",
              "merchant_biz_num": "1234567890",
              "deduction_type": "deductible",
              "supply_amount": 145455,
              "tax_amount": 14545,
              "approval_amount": 160000
            }
          },
          {
            "date": "2026-01-15",
            "description": "직원 야근 식대",
            "trading_partner_name": null,
            "biz_no": null,
            "debit_amount": 50000,
            "credit_amount": 0,
            "balance": 210000,
            "source_type": "journal",
            "source_id": 52,
            "card_tx": null
          }
        ],
        "subtotal": { "debit": 210000, "credit": 0 },
        "cumulative": { "debit": 210000, "credit": 0 }
      }
    ],
    "grand_total": {
      "debit": 210000,
      "credit": 0,
      "balance": 210000
    }
  }
}
```

---

### 2.2 관련 API

| API | 용도 | 비고 |
|-----|------|------|
| `GET /api/v1/account-subjects?selectable=true` | 계정과목 목록 (필터 드롭다운) | `selectable=true` → depth=3 입력 가능 계정만 |
| `GET /api/v1/general-journal-entries/{id}` | 전표 상세 (드릴다운 모달) | source_id로 조회 |

---

## 3. TypeScript 인터페이스

```typescript
// 계정별원장 응답
interface AccountLedgerResponse {
  success: boolean;
  message: string;
  data: {
    account: {
      code: string;
      name: string;
      category: 'asset' | 'liability' | 'capital' | 'revenue' | 'expense';
    } | null;
    period: {
      start_date: string;
      end_date: string;
    };
    carry_forward: {
      debit: number;
      credit: number;
      balance: number;
    };
    monthly_data: MonthlyData[];
    grand_total: {
      debit: number;
      credit: number;
      balance: number;
    };
  };
}

interface MonthlyData {
  month: string;            // "2026-03"
  items: LedgerItem[];
  subtotal: { debit: number; credit: number };
  cumulative: { debit: number; credit: number };
}

interface LedgerItem {
  date: string;                          // "2026-03-01"
  description: string | null;
  trading_partner_name: string | null;   // 카드거래 시 가맹점명으로 대체됨
  biz_no: string | null;                // 카드거래 시 가맹점 사업자번호로 대체됨
  debit_amount: number;
  credit_amount: number;
  balance: number;                       // 누적 잔액
  source_type: string;                   // 'journal' | 'ecard_transaction' | 'bank_transaction' 등
  source_id: number;                     // 전표 ID
  card_tx: CardTransaction | null;       // 카드거래 상세 (없으면 null)
}

interface CardTransaction {
  card_num: string;           // 카드번호 전체 (프론트에서 마스킹: ····끝4자리)
  card_company_name: string;  // "삼성카드"
  merchant_name: string;      // "스타벅스 강남점"
  merchant_biz_num: string;   // "1234567890"
  deduction_type: string;     // "deductible" | "non_deductible"
  supply_amount: number;      // 공급가액
  tax_amount: number;         // 세액
  approval_amount: number;    // 승인금액 (공급가 + 세액)
}

// 전표 상세 (드릴다운 모달용)
interface JournalEntryDetail {
  id: number;
  entry_no: string;           // "JE-20260311-001"
  entry_date: string;
  entry_type: string;
  description: string | null;
  total_debit: number;
  total_credit: number;
  status: 'draft' | 'confirmed';
  source_type: string | null;
  created_by_name: string | null;
  lines: JournalEntryLine[];
}

interface JournalEntryLine {
  line_no: number;
  dc_type: 'debit' | 'credit';
  account_code: string;
  account_name: string;
  trading_partner_name: string | null;
  debit_amount: number;
  credit_amount: number;
  description: string | null;
}
```

---

## 4. 화면 구현 가이드

### 4.1 화면 구조

```
┌──────────────────────────────────────────────────┐
│ 📖 계정별원장                              [인쇄] │
├──────────────────────────────────────────────────┤
│ 조회기간: [시작일] ~ [종료일]                       │
│ 계정과목: [코드/이름 자동완성 검색]        [조회]   │
├──────────────────────────────────────────────────┤
│ 81100 복리후생비  (2026-01-01 ~ 2026-03-20)      │
├───────┬────────────────┬──────┬──────┬─────┬─────┬───────┤
│ 날짜  │ 적요            │거래처│사업자│차변  │대변  │ 잔액  │
├───────┼────────────────┼──────┼──────┼─────┼─────┼───────┤
│   -   │ 이월잔액        │      │      │  0  │  0  │   0   │
│01-11  │ 💳복리후생 [공제]│스타벅│12345 │160K │     │ 160K  │
│       │ 삼성 ····5678   │      │      │     │     │       │
│01-15  │ 직원 야근 식대  │      │      │ 50K │     │ 210K  │
│-------│---  2026-01 계 ─│──────│──────│210K │  0  │       │
│       │    누 계        │      │      │210K │  0  │       │
│02-03  │ ...             │      │      │     │     │       │
│-------│--- 총 합 계 ────│──────│──────│합계 │합계 │최종잔│
└───────┴────────────────┴──────┴──────┴─────┴─────┴───────┘
```

### 4.2 조회 필터

**계정과목 검색 드롭다운**:

```
GET /api/v1/account-subjects?selectable=true
```

- 텍스트 입력 시 코드 + 이름으로 클라이언트 필터링
- 키보드 방향키(↑↓) + Enter로 선택 가능
- 최대 50개까지 드롭다운 표시
- 선택 후 `코드 이름` 형태로 입력창에 표시 (예: "81100 복리후생비")

**기간 기본값**: 당월 1일 ~ 오늘

### 4.3 테이블 행 유형

| 행 유형 | 조건 | 스타일 |
|---------|------|--------|
| **이월잔액** | `carry_forward.balance !== 0` | 노란 배경, 굵은 글씨 |
| **거래 행** | `items[]` | 일반 + hover 효과, 클릭 가능 |
| **카드거래 행** | `item.card_tx !== null` | 💳 아이콘 + 배지 + 서브텍스트 |
| **월 소계** | 매월 마지막 | 회색 배경, 굵은 글씨 |
| **누계** | 매월 마지막 | 회색 배경, 굵은 글씨 |
| **총합계** | 마지막 | 인디고 배경, 굵은 글씨 |

### 4.4 카드거래 행 표시

`card_tx !== null`인 행은 적요 셀에 추가 정보를 표시한다:

```
┌──────────────────────────────────────────────┐
│ 💳 복리후생비                    ┌─────────┐  │
│                                 │  공제   │  │  ← deduction_type === 'deductible'
│ 삼성카드 ····5678               └─────────┘  │  ← card_company_name + 끝4자리
└──────────────────────────────────────────────┘
```

- `deduction_type === 'deductible'` → 초록 배지 `[공제]`
- `deduction_type === 'non_deductible'` → 빨간 배지 `[불공제]`
- 카드번호 마스킹: `'····' + card_tx.card_num.slice(-4)`

### 4.5 드릴다운 (전표 상세 모달)

거래 행 클릭 시 `source_type`에 따라 전표 상세를 모달로 표시:

```javascript
const handleRowClick = async (item: LedgerItem) => {
  if (['journal', 'ecard_transaction', 'bank_transaction'].includes(item.source_type)) {
    const res = await authenticatedFetch(`/api/v1/general-journal-entries/${item.source_id}`);
    // → 전표 상세 모달 오픈
    //   - 전표 헤더: 전표번호, 일자, 적요, 상태
    //   - 분개 라인 테이블: No, 차/대, 계정코드, 계정명, 거래처, 차변, 대변, 적요
    //   - 카드정보 (card_tx가 있으면): 카드번호, 카드사, 가맹점, 공급가, 세액
    //   - [원본 보기] 버튼: 일반전표 화면으로 이동
  }
};
```

**모달 구성**:

| 섹션 | 내용 |
|------|------|
| **헤더** | 전표번호, 일자, [원본 보기] 버튼, [닫기] 버튼 |
| **전표 요약** | 전표번호, 일자, 적요, 작성자, 상태, 출처(source_type) |
| **카드정보** (card_tx 있을 때) | 카드번호(마스킹), 카드사, 가맹점, 사업자번호, 공급가, 세액, [공제]/[불공제] |
| **분개 테이블** | No, 구분(차/대), 계정코드, 계정명, 거래처, 차변, 대변, 적요 |
| **합계** | 차변 합계, 대변 합계 |

### 4.6 잔액 계산

서버에서 이미 계산된 `balance` 값을 사용한다. 프론트에서 별도 계산 불필요.

**음수 잔액 표시**: `(100,000)` 괄호 형식 또는 음수 표시

### 4.7 숫자 포맷

```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();
};
```

- 0은 빈 문자열로 표시 (공란)
- 음수는 괄호 표시
- 천 단위 콤마

### 4.8 인쇄

- `window.print()` 사용
- CSS `@media print { .no-print { display: none !important; } }`
- 조회 조건 패널, 인쇄 버튼에 `no-print` 클래스 적용

---

## 5. MNG 참고 화면

MNG 개발서버에서 동일 기능을 확인할 수 있다:

> `https://admin.codebridge-x.com` → 회계/세무관리 > 계정별원장

---

## 관련 문서

- [dev/dev_plans/account-ledger-service-migration-plan.md](../../dev/dev_plans/account-ledger-service-migration-plan.md) — 이관 기획서
- [features/finance/README.md](../../features/finance/README.md) — 재무관리 기능 개요
- [frontend/api-specs/barobill-api.md](barobill-api.md) — 바로빌 회계 API 명세

---

**최종 업데이트**: 2026-03-20
