Files
sam-docs/dev/dev_plans/condolence-expense-service-plan.md
김보곤 2e3ee9fc92 docs: [finance] 경조사비 서비스 이관 기획서 추가
- MNG 경조사비 → API+React 이관 기획
- API 엔드포인트 7개, React 페이지 구성 설계
- INDEX.md에 문서 등록
2026-03-19 15:40:31 +09:00

16 KiB

경조사비 서비스 이관 기획서

작성일: 2026-03-19 상태: 기획 확정 대상: MNG → API + React (서비스 이관) 위치: 서비스 > 회계관리 > 경조사비


1. 개요

1.1 목적

MNG 백오피스의 경조사비관리 기능을 서비스(API + React)로 이관한다. 기존 MNG의 HTMX + Alpine.js 기반 UI를 REST API + React 구조로 재구현하며, 멀티테넌트 정책을 강화하고 사용자(테넌트) 직접 사용이 가능하도록 한다.

1.2 배경

  • MNG 회계/세무관리 > 경조사비관리 메뉴로 기존 구현 완료 (2026-03-06)
  • DB 테이블 condolence_expenses는 API 프로젝트에서 마이그레이션 관리 중
  • MNG는 scopeForTenant() 수동 스코프 사용 → API는 BelongsToTenant 글로벌 스코프로 전환
  • MNG Controller에 비즈니스 로직이 직접 작성됨 → API는 Service-First 패턴 적용

1.3 이관 범위

구분 MNG (현재) API + React (이관 후)
백엔드 Controller 직접 로직 Service + Controller + FormRequest
프론트 Blade + Alpine.js React (Next.js)
인증 세션 기반 Bearer 토큰 + BelongsToTenant
데이터 scopeForTenant() 수동 글로벌 스코프 자동 격리
API 문서 없음 Swagger

2. 현재 MNG 기능 분석

2.1 DB 테이블 (condolence_expenses)

마이그레이션: api/database/migrations/2026_03_06_220000_create_condolence_expenses_table.php

컬럼 타입 설명
id BIGINT PK 고유 ID
tenant_id BIGINT (FK, INDEX) 테넌트 ID
event_date DATE 경조사 발생일
expense_date DATE 지출일
partner_name VARCHAR(100) 거래처명/대상자
description VARCHAR(200) 내역
category VARCHAR(20) congratulation(축의) / condolence(부조)
has_cash BOOLEAN 부조금 여부
cash_method VARCHAR(30) cash / transfer / card
cash_amount INTEGER 부조금액
has_gift BOOLEAN 선물 여부
gift_type VARCHAR(50) 선물 종류
gift_amount INTEGER 선물 금액
total_amount INTEGER 총금액 (부조금 + 선물)
options JSON 확장 속성
memo TEXT 비고
created_by BIGINT 등록자
created_at / updated_at TIMESTAMP 타임스탬프
deleted_at TIMESTAMP 소프트 삭제

인덱스: (tenant_id, event_date), (tenant_id, category)

2.2 MNG 기능 목록

기능 MNG 메서드 설명
목록 조회 list() 연도/구분/검색 필터 + 통계
등록 store() 경조사비 신규 등록
수정 update() 기존 항목 수정
삭제 destroy() 소프트 삭제
CSV 내보내기 JS exportCsv() 프론트엔드에서 직접 생성

2.3 통계 응답 (stats)

{
  "totalCount": 12,
  "totalAmount": 1250000,
  "cashTotal": 750000,
  "giftTotal": 500000,
  "congratulationCount": 7,
  "condolenceCount": 5
}

3. API 설계

3.1 엔드포인트

라우트 파일: api/routes/api/v1/finance.php 기본 경로: /api/v1/condolence-expenses

Method Path 설명 비고
GET / 목록 조회 (페이지네이션) 필터: year, category, search
POST / 신규 등록
GET /summary 통계 조회 연도/구분별 집계
GET /{id} 상세 조회
PUT /{id} 수정
DELETE /{id} 삭제 (소프트)
GET /export CSV/Excel 내보내기 서버사이드 생성

3.2 목록 조회 요청/응답

요청 파라미터:

파라미터 타입 필수 설명 기본값
year integer N 연도 필터 당해연도
category string N congratulation / condolence 전체
search string N 거래처명/내역/비고 통합 검색 -
page integer N 페이지 번호 1
per_page integer N 페이지 크기 50
sort_by string N 정렬 기준 event_date
sort_order string N asc / desc desc

응답:

{
  "success": true,
  "data": [
    {
      "id": 1,
      "event_date": "2026-03-15",
      "expense_date": "2026-03-16",
      "partner_name": "ABC 회사",
      "description": "김과장 결혼축의금",
      "category": "congratulation",
      "category_label": "축의",
      "has_cash": true,
      "cash_method": "transfer",
      "cash_method_label": "계좌이체",
      "cash_amount": 50000,
      "has_gift": true,
      "gift_type": "화환",
      "gift_amount": 30000,
      "total_amount": 80000,
      "memo": "사원 본인 결혼",
      "created_by_name": "홍길동",
      "created_at": "2026-03-16T10:30:00"
    }
  ],
  "meta": { "current_page": 1, "last_page": 1, "per_page": 50, "total": 12 }
}

3.3 통계 조회 (/summary)

요청: ?year=2026&category=all

응답:

{
  "success": true,
  "data": {
    "total_count": 12,
    "total_amount": 1250000,
    "cash_total": 750000,
    "gift_total": 500000,
    "congratulation_count": 7,
    "condolence_count": 5,
    "congratulation_amount": 800000,
    "condolence_amount": 450000
  }
}

3.4 등록/수정 요청

{
  "event_date": "2026-03-15",
  "expense_date": "2026-03-16",
  "partner_name": "ABC 회사",
  "description": "김과장 결혼축의금",
  "category": "congratulation",
  "has_cash": true,
  "cash_method": "transfer",
  "cash_amount": 50000,
  "has_gift": true,
  "gift_type": "화환",
  "gift_amount": 30000,
  "memo": "사원 본인 결혼"
}

total_amount는 서버에서 자동 계산 (클라이언트 전송 불필요)

3.5 검증 규칙 (FormRequest)

필드 규칙
partner_name 필수, string, max:100
category 필수, in:congratulation,condolence
event_date 선택, date
expense_date 선택, date
description 선택, string, max:200
has_cash 선택, boolean
cash_method has_cash=true일 때 필수, in:cash,transfer,card
cash_amount has_cash=true일 때 필수, integer, min:0
has_gift 선택, boolean
gift_type has_gift=true일 때 선택, string, max:50
gift_amount has_gift=true일 때 필수, integer, min:0
memo 선택, string

4. API 구현 상세

4.1 파일 구조

api/
├── app/
│   ├── Http/
│   │   ├── Controllers/Api/V1/CondolenceExpenseController.php
│   │   └── Requests/V1/CondolenceExpense/
│   │       ├── StoreCondolenceExpenseRequest.php
│   │       └── UpdateCondolenceExpenseRequest.php
│   ├── Models/Tenants/CondolenceExpense.php
│   ├── Services/CondolenceExpenseService.php
│   └── Swagger/v1/CondolenceExpenseApi.php
└── routes/api/v1/finance.php  (라우트 추가)

4.2 Model

class CondolenceExpense extends Model
{
    use Auditable, BelongsToTenant, ModelTrait, SoftDeletes;

    // 카테고리 상수
    const CATEGORY_CONGRATULATION = 'congratulation';
    const CATEGORY_CONDOLENCE = 'condolence';

    // 지출방법 상수
    const CASH_METHOD_CASH = 'cash';
    const CASH_METHOD_TRANSFER = 'transfer';
    const CASH_METHOD_CARD = 'card';

    protected $casts = [
        'event_date' => 'date',
        'expense_date' => 'date',
        'has_cash' => 'boolean',
        'has_gift' => 'boolean',
        'cash_amount' => 'integer',
        'gift_amount' => 'integer',
        'total_amount' => 'integer',
        'options' => 'array',
    ];

    // Accessor: category_label
    // Accessor: cash_method_label
    // Scope: scopeByCategory(), scopeInYear()
    // Relation: creator() → User (created_by)
}

4.3 Service 메서드

메서드 설명
index(array $params) 페이지네이션 + 필터 (year, category, search, sort)
store(array $data) 등록 (total_amount 자동 계산, created_by 설정)
show(int $id) 상세 조회
update(int $id, array $data) 수정 (total_amount 재계산)
destroy(int $id) 소프트 삭제
summary(array $params) 통계 집계 (year, category 필터)
export(array $params) CSV/Excel 데이터 생성

4.4 total_amount 자동 계산

private function calculateTotal(array $data): int
{
    $cash = ($data['has_cash'] ?? false) ? ($data['cash_amount'] ?? 0) : 0;
    $gift = ($data['has_gift'] ?? false) ? ($data['gift_amount'] ?? 0) : 0;
    return $cash + $gift;
}

4.5 마이그레이션 추가 필요 여부

기존 테이블: condolence_expenses (이미 존재) 추가 마이그레이션: updated_by, deleted_by 컬럼 추가 (Auditable 트레이트 호환)

// 추가 마이그레이션 필요
Schema::table('condolence_expenses', function (Blueprint $table) {
    $table->unsignedBigInteger('updated_by')->nullable()->after('created_by');
    $table->unsignedBigInteger('deleted_by')->nullable()->after('updated_by');
});

5. React 구현 상세

5.1 메뉴 위치

회계관리 (accounting)
├── 입금관리
├── 출금관리
├── 카드거래조회
├── 은행거래조회
├── ...
├── 경조사비              ← 신규 (NEW)
├── 상품권
└── ...

React 라우트: /accounting/condolence-expenses

5.2 파일 구조

react/src/
├── app/[locale]/(protected)/accounting/
│   └── condolence-expenses/
│       └── page.tsx              (진입점: mode 분기)
├── components/accounting/
│   └── CondolenceExpenseManagement/
│       ├── index.tsx             (메인 컴포넌트)
│       ├── CondolenceExpenseList.tsx   (목록 + 필터 + 통계)
│       ├── CondolenceExpenseForm.tsx   (등록/수정 모달)
│       ├── actions.ts            (Server Actions)
│       └── types.ts              (타입 정의)

5.3 페이지 구성

통계 카드 (상단)

카드 스타일
총 건수 total_count -
총 금액 total_amount 통화 포맷
부조금 합계 cash_total 통화 포맷
선물 합계 gift_total 통화 포맷
축의/부조 congratulation_count / condolence_count 건수

필터 (통계 카드 하단)

필터 타입 옵션
연도 Select 당해 ~ 5년 전
구분 Select 전체 / 축의 / 부조
검색 Input 거래처명, 내역, 비고 (debounce 300ms)

테이블

No 컬럼 정렬
1 경조사일자
2 지출일자
3 거래처명
4 내역
5 구분 중앙 (배지: 축의=빨강, 부조=회색)
6 부조금 여부 중앙
7 지출방법
8 부조금액 우 (통화)
9 선물 여부 중앙
10 선물종류
11 선물금액 우 (통화)
12 총금액 우 (통화, 굵게)
13 비고

하단 합계 행: 부조금액 합계, 선물금액 합계, 총금액 합계

등록/수정 모달

MNG 모달 구조를 React Dialog로 재현:

  1. 날짜 (경조사일자, 지출일자)
  2. 거래처명 (필수), 내역, 구분 (축의/부조)
  3. 부조금 섹션 (체크박스 토글) — 지출방법, 금액
  4. 선물 섹션 (체크박스 토글) — 종류, 금액
  5. 총금액 (읽기 전용, 자동 계산)
  6. 비고

5.4 Server Actions (actions.ts)

// buildApiUrl 필수 사용
import { buildApiUrl } from '@/lib/api/query-params';

export async function getCondolenceExpenses(params) {
  // GET /api/v1/condolence-expenses
}

export async function getCondolenceExpenseSummary(params) {
  // GET /api/v1/condolence-expenses/summary
}

export async function createCondolenceExpense(data) {
  // POST /api/v1/condolence-expenses
}

export async function updateCondolenceExpense(id, data) {
  // PUT /api/v1/condolence-expenses/{id}
}

export async function deleteCondolenceExpense(id) {
  // DELETE /api/v1/condolence-expenses/{id}
}

6. 인증 및 권한

6.1 API 인증

  • 기본 미들웨어: auth.apikey (API Key + Bearer 토큰)
  • BelongsToTenant 글로벌 스코프로 자동 데이터 격리

6.2 MNG → API 호출 시

경조사비 API는 Bearer 토큰 필수 (화이트리스트 등록 불필요) MNG에서 경조사비를 계속 사용하려면 FormulaApiService 패턴으로 API 호출 필요

6.3 React 인증

  • authenticatedFetch 사용 (HttpOnly 쿠키 기반)
  • 직접 fetch 금지

7. 구현 순서

Phase 1: API 구현

순서 작업 파일
1 마이그레이션 (updated_by, deleted_by 추가) database/migrations/
2 Model 생성 app/Models/Tenants/CondolenceExpense.php
3 Service 생성 app/Services/CondolenceExpenseService.php
4 FormRequest 생성 app/Http/Requests/V1/CondolenceExpense/
5 Controller 생성 app/Http/Controllers/Api/V1/CondolenceExpenseController.php
6 Route 등록 routes/api/v1/finance.php
7 Swagger 작성 app/Swagger/v1/CondolenceExpenseApi.php

Phase 2: React 구현

순서 작업 파일
1 타입 정의 types.ts
2 Server Actions actions.ts
3 목록 컴포넌트 (통계 + 필터 + 테이블) CondolenceExpenseList.tsx
4 등록/수정 모달 CondolenceExpenseForm.tsx
5 페이지 진입점 page.tsx
6 메뉴 등록 DB (tinker)

Phase 3: 연동 테스트

순서 작업
1 API Swagger UI 테스트
2 React ↔ API 연동 확인
3 멀티테넌트 데이터 격리 검증
4 기존 MNG 데이터 React에서 조회 확인

8. MNG 기존 코드 처리

MNG 경조사비 코드는 삭제하지 않는다.

항목 처리
MNG Controller 유지 (관리자 전용 조회 가능)
MNG Model 유지
MNG Blade View 유지
MNG Route 유지

이관 완료 후 MNG 메뉴에서 경조사비를 숨기고, 서비스(React)로 안내한다.


9. 기존 패턴 참고

참고 기능 위치 참고 사항
BankAccount api/app/Services/BankAccountService.php CRUD + toggle + setPrimary 패턴
ExpenseAccount api/app/Models/Tenants/ExpenseAccount.php 복리후생비 카테고리 구분
Deposit/Withdrawal api/app/Models/Tenants/Deposit.php 입출금 CRUD 패턴
급여관리 docs/features/finance/payroll.md 상태 워크플로우, 전표 연동

10. 체크리스트

API 구현 체크리스트

  • Model: BelongsToTenant, Auditable, SoftDeletes trait 적용
  • Model: 'options' => 'array' cast + getOption()/setOption() 헬퍼
  • Model: 카테고리/지출방법 상수 정의
  • Service: $this->tenantId(), $this->apiUserId() 사용
  • Service: total_amount 자동 계산 로직
  • Controller: ApiResponse::handle() 사용
  • FormRequest: 검증 규칙 분리 (Store/Update)
  • Route: finance.php에 등록
  • Swagger: 엔드포인트 문서 작성
  • 마이그레이션: updated_by, deleted_by 컬럼 추가

React 구현 체크리스트

  • 'use client' 선언
  • buildApiUrl() 사용 (직접 URL 조립 금지)
  • authenticatedFetch / Server Actions 사용
  • 통계 카드 + 필터 + 테이블 + 합계행
  • 등록/수정 모달 (Dialog)
  • 금액 포맷팅 (천 단위 구분)
  • debounce 검색 (300ms)
  • 메뉴 등록 (DB tinker)

인증 체크리스트

  • API: Bearer 토큰 필수 (화이트리스트 불필요)
  • React: authenticatedFetch 사용
  • 멀티테넌트 데이터 격리 검증

관련 문서

문서 경로
API 개발 규칙 dev/standards/api-rules.md
options 컬럼 정책 standards/options-column-policy.md
이관 현황 system/migration-status.md
급여관리 features/finance/payroll.md
보안 정책 system/security-policy.md
계정별원장 기획 dev/dev_plans/account-ledger-income-statement-plan.md

최종 업데이트: 2026-03-19