14 KiB
14 KiB
경조사비 관리 API 명세
작성일: 2026-03-19 상태: API 구현 완료 대상: React 프론트엔드 개발자 API 기본 경로:
/api/v1/condolence-expensesReact 페이지 경로:/accounting/condolence-expenses메뉴 위치: 회계관리 > 경조사비
1. 개요
거래처/임직원 경조사비를 관리하는 CRUD API. 축의(congratulation)와 부조(condolence)를 구분하며, 부조금과 선물을 각각 관리한다.
2. API 엔드포인트
| Method | Path | 설명 |
|---|---|---|
| GET | / |
목록 조회 (페이지네이션) |
| POST | / |
신규 등록 |
| GET | /summary |
통계 조회 |
| GET | /{id} |
상세 조회 |
| PUT | /{id} |
수정 |
| DELETE | /{id} |
삭제 (소프트) |
3. 목록 조회
GET /api/v1/condolence-expenses
요청 파라미터 (Query String):
| 파라미터 | 타입 | 필수 | 설명 | 기본값 |
|---|---|---|---|---|
year |
number | N | 연도 필터 (event_date 기준) | - |
category |
string | N | congratulation / condolence / all |
전체 |
search |
string | N | 거래처명/내역/비고 통합 검색 | - |
sort_by |
string | N | 정렬 기준 | event_date |
sort_order |
string | N | asc / desc |
desc |
per_page |
number | N | 페이지 크기 | 50 |
page |
number | N | 페이지 번호 | 1 |
응답:
{
"success": true,
"message": "조회되었습니다.",
"data": [
{
"id": 1,
"tenant_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,
"options": null,
"memo": "사원 본인 결혼",
"created_by": 5,
"updated_by": 5,
"created_at": "2026-03-16T10:30:00.000000Z",
"updated_at": "2026-03-16T10:30:00.000000Z",
"deleted_at": null
}
],
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 50,
"total": 12
}
}
4. 통계 조회
GET /api/v1/condolence-expenses/summary
요청 파라미터 (Query String):
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
year |
number | N | 연도 필터 |
category |
string | N | congratulation / condolence / 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
}
}
5. 등록
POST /api/v1/condolence-expenses
요청 Body (JSON):
{
"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": "사원 본인 결혼"
}
검증 규칙:
| 필드 | 타입 | 필수 | 규칙 |
|---|---|---|---|
event_date |
string (date) | N | YYYY-MM-DD |
expense_date |
string (date) | N | YYYY-MM-DD |
partner_name |
string | Y | max 100자 |
description |
string | N | max 200자 |
category |
string | Y | congratulation / condolence |
has_cash |
boolean | N | default: false |
cash_method |
string | has_cash=true일 때 필수 |
cash / transfer / card |
cash_amount |
number | has_cash=true일 때 필수 |
0 이상 정수 |
has_gift |
boolean | N | default: false |
gift_type |
string | N | max 50자 |
gift_amount |
number | has_gift=true일 때 필수 |
0 이상 정수 |
memo |
string | N | - |
total_amount는 서버에서 자동 계산 (cash_amount + gift_amount)
응답 (201):
{
"success": true,
"message": "등록되었습니다.",
"data": { /* 생성된 객체 */ }
}
6. 상세 조회
GET /api/v1/condolence-expenses/{id}
응답: 목록 조회의 data 항목과 동일 구조 + creator 관계 포함
7. 수정
PUT /api/v1/condolence-expenses/{id}
등록과 동일한 Body 구조. partner_name, category는 sometimes|required.
응답:
{
"success": true,
"message": "수정되었습니다.",
"data": { /* 수정된 객체 */ }
}
8. 삭제
DELETE /api/v1/condolence-expenses/{id}
소프트 삭제 (deleted_at 기록).
응답:
{
"success": true,
"message": "삭제되었습니다.",
"data": null
}
9. TypeScript 타입 정의
/** 경조사비 카테고리 */
type CondolenceCategory = 'congratulation' | 'condolence';
/** 지출방법 */
type CashMethod = 'cash' | 'transfer' | 'card';
/** 경조사비 항목 */
interface CondolenceExpense {
id: number;
tenant_id: number;
event_date: string | null; // "YYYY-MM-DD"
expense_date: string | null; // "YYYY-MM-DD"
partner_name: string;
description: string | null;
category: CondolenceCategory;
category_label: string; // "축의" | "부조"
has_cash: boolean;
cash_method: CashMethod | null;
cash_method_label: string | null; // "현금" | "계좌이체" | "카드"
cash_amount: number;
has_gift: boolean;
gift_type: string | null;
gift_amount: number;
total_amount: number;
options: Record<string, unknown> | null;
memo: string | null;
created_by: number | null;
updated_by: number | null;
created_at: string;
updated_at: string;
deleted_at: string | null;
}
/** 통계 */
interface CondolenceExpenseSummary {
total_count: number;
total_amount: number;
cash_total: number;
gift_total: number;
congratulation_count: number;
condolence_count: number;
congratulation_amount: number;
condolence_amount: number;
}
/** 등록/수정 요청 */
interface CondolenceExpenseForm {
event_date?: string | null;
expense_date?: string | null;
partner_name: string;
description?: string | null;
category: CondolenceCategory;
has_cash?: boolean;
cash_method?: CashMethod | null;
cash_amount?: number;
has_gift?: boolean;
gift_type?: string | null;
gift_amount?: number;
memo?: string | null;
}
/** 목록 조회 파라미터 */
interface CondolenceExpenseListParams {
year?: number;
category?: CondolenceCategory | 'all';
search?: string;
sort_by?: string;
sort_order?: 'asc' | 'desc';
per_page?: number;
page?: number;
}
10. 화면 구성 가이드
10.1 메뉴 위치
회계관리 (accounting)
└── 경조사비 (/accounting/condolence-expenses)
10.2 페이지 레이아웃
┌─────────────────────────────────────────────┐
│ 경조사비 관리 [+ 등록] │
├─────────────────────────────────────────────┤
│ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │총 건수 │ │총 금액 │ │부조금 │ │선물 │ │
│ │ 12건 │ │125만원 │ │ 75만원 │ │50만원 │ │
│ └───────┘ └───────┘ └───────┘ └───────┘ │
├─────────────────────────────────────────────┤
│ 연도 [2026▾] 구분 [전체▾] 검색 [________] │
├─────────────────────────────────────────────┤
│ No│경조사일│지출일│거래처│내역│구분│부조금│...│
│ 1│03-15 │03-16 │ABC │결혼│축의│50,000│...│
│ 2│03-10 │03-11 │DEF │상 │부조│100K │...│
│───┼───────┼──────┼─────┼────┼────┼──────┼───│
│ │ │ │ │합계│ │150K │...│
└─────────────────────────────────────────────┘
10.3 통계 카드 (상단)
| 카드 | API 필드 | 포맷 |
|---|---|---|
| 총 건수 | total_count |
N건 |
| 총 금액 | total_amount |
통화 (예: 1,250,000원) |
| 부조금 합계 | cash_total |
통화 |
| 선물 합계 | gift_total |
통화 |
추가로 축의/부조 건수 표시: congratulation_count / condolence_count
10.4 필터
| 필터 | 컴포넌트 | 옵션 |
|---|---|---|
| 연도 | Select | 당해연도 ~ 5년 전 |
| 구분 | Select | 전체 / 축의 / 부조 |
| 검색 | Input | debounce 300ms, placeholder: "거래처명, 내역, 비고" |
필터 변경 시 목록 + 통계 동시 조회
10.5 테이블 컬럼
| No | 컬럼명 | 필드 | 정렬 | 비고 |
|---|---|---|---|---|
| 1 | 경조사일자 | event_date |
좌 | YYYY-MM-DD |
| 2 | 지출일자 | expense_date |
좌 | YYYY-MM-DD |
| 3 | 거래처명 | partner_name |
좌 | |
| 4 | 내역 | description |
좌 | |
| 5 | 구분 | category_label |
중앙 | Badge: 축의=red, 부조=gray |
| 6 | 부조금 | has_cash |
중앙 | 여/부 표시 |
| 7 | 지출방법 | cash_method_label |
좌 | |
| 8 | 부조금액 | cash_amount |
우 | 통화 포맷 |
| 9 | 선물 | has_gift |
중앙 | 여/부 표시 |
| 10 | 선물종류 | gift_type |
좌 | |
| 11 | 선물금액 | gift_amount |
우 | 통화 포맷 |
| 12 | 총금액 | total_amount |
우 | 굵게, 통화 포맷 |
| 13 | 비고 | memo |
좌 |
하단 합계 행: 부조금액 합계, 선물금액 합계, 총금액 합계 (클라이언트 계산)
10.6 등록/수정 모달 (Dialog)
필드 구성:
| 섹션 | 필드 | 컴포넌트 | 필수 |
|---|---|---|---|
| 날짜 | 경조사일자 | DatePicker | N |
| 날짜 | 지출일자 | DatePicker | N |
| 기본 | 거래처명/대상자 | Input | Y |
| 기본 | 내역 | Input | N |
| 기본 | 구분 | Select (축의/부조) | Y |
| 부조금 | 부조금 여부 | Checkbox (토글) | N |
| 부조금 | 지출방법 | Select (현금/계좌이체/카드) | 조건부 |
| 부조금 | 금액 | NumberInput (통화 포맷) | 조건부 |
| 선물 | 선물 여부 | Checkbox (토글) | N |
| 선물 | 종류 | Input | N |
| 선물 | 금액 | NumberInput (통화 포맷) | 조건부 |
| 합계 | 총금액 | 읽기 전용 (자동 계산) | - |
| 메모 | 비고 | Textarea | N |
동작:
- 부조금 체크 해제 시 → 지출방법/금액 숨김 + 값 초기화
- 선물 체크 해제 시 → 종류/금액 숨김 + 값 초기화
- 총금액 = 부조금액 + 선물금액 (실시간 계산)
- 등록 성공 시 → 모달 닫기 + 목록 + 통계 새로고침
10.7 행 액션
| 액션 | 동작 |
|---|---|
| 행 클릭 또는 수정 버튼 | 수정 모달 열기 (데이터 로드) |
| 삭제 버튼 | 확인 다이얼로그 → DELETE API 호출 |
11. Server Actions 예시
// actions.ts
'use server';
import { buildApiUrl } from '@/lib/api/query-params';
import { executeServerAction, executePaginatedAction } from '@/lib/api/server-action-utils';
export async function getCondolenceExpenses(params: CondolenceExpenseListParams) {
return executePaginatedAction({
url: buildApiUrl('/api/v1/condolence-expenses', {
year: params.year,
category: params.category !== 'all' ? params.category : undefined,
search: params.search,
sort_by: params.sort_by,
sort_order: params.sort_order,
per_page: params.per_page,
page: params.page,
}),
method: 'GET',
});
}
export async function getCondolenceExpenseSummary(params: { year?: number; category?: string }) {
return executeServerAction({
url: buildApiUrl('/api/v1/condolence-expenses/summary', {
year: params.year,
category: params.category !== 'all' ? params.category : undefined,
}),
method: 'GET',
});
}
export async function createCondolenceExpense(data: CondolenceExpenseForm) {
return executeServerAction({
url: buildApiUrl('/api/v1/condolence-expenses'),
method: 'POST',
body: data,
});
}
export async function updateCondolenceExpense(id: number, data: CondolenceExpenseForm) {
return executeServerAction({
url: buildApiUrl(`/api/v1/condolence-expenses/${id}`),
method: 'PUT',
body: data,
});
}
export async function deleteCondolenceExpense(id: number) {
return executeServerAction({
url: buildApiUrl(`/api/v1/condolence-expenses/${id}`),
method: 'DELETE',
});
}
12. 카테고리/지출방법 라벨 매핑
const CATEGORY_OPTIONS = [
{ value: 'congratulation', label: '축의' },
{ value: 'condolence', label: '부조' },
];
const CASH_METHOD_OPTIONS = [
{ value: 'cash', label: '현금' },
{ value: 'transfer', label: '계좌이체' },
{ value: 'card', label: '카드' },
];
관련 문서
| 문서 | 경로 |
|---|---|
| 기획서 | dev/dev_plans/condolence-expense-service-plan.md |
| MNG 기존 구현 | mng/app/Http/Controllers/Finance/CondolenceExpenseController.php |
| MNG Blade 뷰 | mng/resources/views/finance/condolence-expenses.blade.php |
최종 업데이트: 2026-03-19