- 40+ actions.ts 파일을 fetchWrapper 패턴으로 마이그레이션 - 토큰 리프레시 캐싱 로직 추가 (refresh-token.ts) - ApiErrorContext 추가로 전역 에러 처리 개선 - HR EmployeeForm 컴포넌트 개선 - 참조함(ReferenceBox) 기능 수정 - juil 테스트 URL 페이지 추가 - claudedocs 문서 업데이트 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
280 lines
8.0 KiB
TypeScript
280 lines
8.0 KiB
TypeScript
'use server';
|
|
|
|
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
|
import type { BankTransaction, TransactionKind } from './types';
|
|
|
|
// ===== API 응답 타입 =====
|
|
interface BankTransactionApiItem {
|
|
id: number;
|
|
type: 'deposit' | 'withdrawal';
|
|
transaction_date: string;
|
|
bank_account_id: number;
|
|
bank_name: string;
|
|
account_name: string;
|
|
note: string | null;
|
|
vendor_id: number | null;
|
|
vendor_name: string | null;
|
|
depositor_name: string | null;
|
|
deposit_amount: number | string;
|
|
withdrawal_amount: number | string;
|
|
balance: number | string;
|
|
transaction_type: string | null;
|
|
source_id: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
interface BankTransactionApiSummary {
|
|
total_deposit: number;
|
|
total_withdrawal: number;
|
|
deposit_unset_count: number;
|
|
withdrawal_unset_count: number;
|
|
}
|
|
|
|
interface BankAccountOption {
|
|
id: number;
|
|
label: string;
|
|
}
|
|
|
|
interface PaginationMeta {
|
|
current_page: number;
|
|
last_page: number;
|
|
per_page: number;
|
|
total: number;
|
|
}
|
|
|
|
// ===== API → Frontend 변환 =====
|
|
function transformItem(item: BankTransactionApiItem): BankTransaction {
|
|
return {
|
|
id: String(item.id),
|
|
bankName: item.bank_name,
|
|
accountName: item.account_name,
|
|
transactionDate: item.transaction_date,
|
|
type: item.type as TransactionKind,
|
|
note: item.note || undefined,
|
|
vendorId: item.vendor_id ? String(item.vendor_id) : undefined,
|
|
vendorName: item.vendor_name || undefined,
|
|
depositorName: item.depositor_name || undefined,
|
|
depositAmount: typeof item.deposit_amount === 'string' ? parseFloat(item.deposit_amount) : item.deposit_amount,
|
|
withdrawalAmount: typeof item.withdrawal_amount === 'string' ? parseFloat(item.withdrawal_amount) : item.withdrawal_amount,
|
|
balance: typeof item.balance === 'string' ? parseFloat(item.balance) : item.balance,
|
|
transactionType: item.transaction_type || undefined,
|
|
sourceId: item.source_id,
|
|
createdAt: item.created_at,
|
|
updatedAt: item.updated_at,
|
|
};
|
|
}
|
|
|
|
// ===== 입출금 통합 목록 조회 =====
|
|
export async function getBankTransactionList(params?: {
|
|
page?: number;
|
|
perPage?: number;
|
|
startDate?: string;
|
|
endDate?: string;
|
|
bankAccountId?: number;
|
|
transactionType?: string;
|
|
search?: string;
|
|
sortBy?: string;
|
|
sortDir?: 'asc' | 'desc';
|
|
}): Promise<{
|
|
success: boolean;
|
|
data: BankTransaction[];
|
|
pagination: {
|
|
currentPage: number;
|
|
lastPage: number;
|
|
perPage: number;
|
|
total: number;
|
|
};
|
|
error?: string;
|
|
}> {
|
|
try {
|
|
const searchParams = new URLSearchParams();
|
|
|
|
if (params?.page) searchParams.set('page', String(params.page));
|
|
if (params?.perPage) searchParams.set('per_page', String(params.perPage));
|
|
if (params?.startDate) searchParams.set('start_date', params.startDate);
|
|
if (params?.endDate) searchParams.set('end_date', params.endDate);
|
|
if (params?.bankAccountId) searchParams.set('bank_account_id', String(params.bankAccountId));
|
|
if (params?.transactionType) searchParams.set('transaction_type', params.transactionType);
|
|
if (params?.search) searchParams.set('search', params.search);
|
|
if (params?.sortBy) searchParams.set('sort_by', params.sortBy);
|
|
if (params?.sortDir) searchParams.set('sort_dir', params.sortDir);
|
|
|
|
const queryString = searchParams.toString();
|
|
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/bank-transactions${queryString ? `?${queryString}` : ''}`;
|
|
|
|
const { response, error } = await serverFetch(url, { method: 'GET' });
|
|
|
|
if (error) {
|
|
return {
|
|
success: false,
|
|
data: [],
|
|
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
|
|
error: error.message,
|
|
};
|
|
}
|
|
|
|
if (!response?.ok) {
|
|
console.warn('[BankTransactionActions] GET bank-transactions error:', response?.status);
|
|
return {
|
|
success: false,
|
|
data: [],
|
|
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
|
|
error: `API 오류: ${response?.status}`,
|
|
};
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!result.success) {
|
|
return {
|
|
success: false,
|
|
data: [],
|
|
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
|
|
error: result.message || '은행 거래 조회에 실패했습니다.',
|
|
};
|
|
}
|
|
|
|
const paginationData = result.data;
|
|
const items = (paginationData?.data || []).map(transformItem);
|
|
const meta: PaginationMeta = {
|
|
current_page: paginationData?.current_page || 1,
|
|
last_page: paginationData?.last_page || 1,
|
|
per_page: paginationData?.per_page || 20,
|
|
total: paginationData?.total || items.length,
|
|
};
|
|
|
|
return {
|
|
success: true,
|
|
data: items,
|
|
pagination: {
|
|
currentPage: meta.current_page,
|
|
lastPage: meta.last_page,
|
|
perPage: meta.per_page,
|
|
total: meta.total,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error('[BankTransactionActions] getBankTransactionList error:', error);
|
|
return {
|
|
success: false,
|
|
data: [],
|
|
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
|
|
error: '서버 오류가 발생했습니다.',
|
|
};
|
|
}
|
|
}
|
|
|
|
// ===== 입출금 요약 통계 =====
|
|
export async function getBankTransactionSummary(params?: {
|
|
startDate?: string;
|
|
endDate?: string;
|
|
}): Promise<{
|
|
success: boolean;
|
|
data?: {
|
|
totalDeposit: number;
|
|
totalWithdrawal: number;
|
|
depositUnsetCount: number;
|
|
withdrawalUnsetCount: number;
|
|
};
|
|
error?: string;
|
|
}> {
|
|
try {
|
|
const searchParams = new URLSearchParams();
|
|
|
|
if (params?.startDate) searchParams.set('start_date', params.startDate);
|
|
if (params?.endDate) searchParams.set('end_date', params.endDate);
|
|
|
|
const queryString = searchParams.toString();
|
|
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/bank-transactions/summary${queryString ? `?${queryString}` : ''}`;
|
|
|
|
const { response, error } = await serverFetch(url, { method: 'GET' });
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
if (!response?.ok) {
|
|
console.warn('[BankTransactionActions] GET summary error:', response?.status);
|
|
return {
|
|
success: false,
|
|
error: `API 오류: ${response?.status}`,
|
|
};
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!result.success) {
|
|
return {
|
|
success: false,
|
|
error: result.message || '요약 조회에 실패했습니다.',
|
|
};
|
|
}
|
|
|
|
const apiSummary: BankTransactionApiSummary = result.data;
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
totalDeposit: apiSummary.total_deposit,
|
|
totalWithdrawal: apiSummary.total_withdrawal,
|
|
depositUnsetCount: apiSummary.deposit_unset_count,
|
|
withdrawalUnsetCount: apiSummary.withdrawal_unset_count,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error('[BankTransactionActions] getBankTransactionSummary error:', error);
|
|
return {
|
|
success: false,
|
|
error: '서버 오류가 발생했습니다.',
|
|
};
|
|
}
|
|
}
|
|
|
|
// ===== 계좌 목록 조회 (필터용) =====
|
|
export async function getBankAccountOptions(): Promise<{
|
|
success: boolean;
|
|
data: { id: number; label: string }[];
|
|
error?: string;
|
|
}> {
|
|
try {
|
|
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/bank-transactions/accounts`;
|
|
|
|
const { response, error } = await serverFetch(url, { method: 'GET' });
|
|
|
|
if (error) {
|
|
return { success: false, data: [], error: error.message };
|
|
}
|
|
|
|
if (!response?.ok) {
|
|
console.warn('[BankTransactionActions] GET accounts error:', response?.status);
|
|
return {
|
|
success: false,
|
|
data: [],
|
|
error: `API 오류: ${response?.status}`,
|
|
};
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!result.success) {
|
|
return {
|
|
success: false,
|
|
data: [],
|
|
error: result.message || '계좌 목록 조회에 실패했습니다.',
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: result.data as BankAccountOption[],
|
|
};
|
|
} catch (error) {
|
|
console.error('[BankTransactionActions] getBankAccountOptions error:', error);
|
|
return {
|
|
success: false,
|
|
data: [],
|
|
error: '서버 오류가 발생했습니다.',
|
|
};
|
|
}
|
|
} |