Files
sam-react-prod/src/components/settings/AccountManagement/actions.ts
유병철 437d5f6834 refactor(WEB): SearchableSelectionModal 공통화 및 actions lookup 통합
- SearchableSelectionModal<T> 제네릭 컴포넌트 추출 (organisms)
- 검색 모달 5개 리팩토링: SupplierSearch, QuotationSelect, SalesOrderSelect, OrderSelect, ItemSearch
- shared-lookups API 유틸 추가 (거래처/품목/수주 등 공통 조회)
- create-crud-service 확장 (lookup, search 메서드)
- actions.ts 20+개 파일 lookup 패턴 통일
- 공통 페이지 패턴 가이드 문서 추가
- CLAUDE.md Common Component Usage Rules 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 16:01:23 +09:00

161 lines
5.9 KiB
TypeScript

'use server';
import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action';
import type { PaginatedApiResponse } from '@/lib/api/types';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import type { Account, AccountFormData, AccountStatus } from './types';
import { BANK_LABELS } from './types';
const API_URL = process.env.NEXT_PUBLIC_API_URL;
// ===== API 응답 타입 =====
interface BankAccountApiData {
id: number;
bank_code: string;
bank_name: string;
account_number: string;
account_holder: string;
account_name: string;
status: AccountStatus;
is_primary: boolean;
assigned_user_id?: number;
created_at?: string;
updated_at?: string;
}
type BankAccountPaginatedResponse = PaginatedApiResponse<BankAccountApiData>;
// ===== 데이터 변환 =====
function transformApiToFrontend(apiData: BankAccountApiData): Account {
return {
id: apiData.id,
bankCode: apiData.bank_code,
bankName: apiData.bank_name || BANK_LABELS[apiData.bank_code] || apiData.bank_code,
accountNumber: apiData.account_number,
accountName: apiData.account_name,
accountHolder: apiData.account_holder,
status: apiData.status,
isPrimary: apiData.is_primary,
assignedUserId: apiData.assigned_user_id,
createdAt: apiData.created_at || '',
updatedAt: apiData.updated_at || '',
};
}
function transformFrontendToApi(data: Partial<AccountFormData>): Record<string, unknown> {
return {
bank_code: data.bankCode,
bank_name: data.bankName || BANK_LABELS[data.bankCode || ''] || data.bankCode,
account_number: data.accountNumber,
account_holder: data.accountHolder,
account_name: data.accountName,
status: data.status,
};
}
// ===== 계좌 목록 조회 =====
export async function getBankAccounts(params?: {
page?: number; perPage?: number; search?: string;
}): Promise<{
success: boolean; data?: Account[]; meta?: { currentPage: number; lastPage: number; perPage: number; total: number };
error?: string; __authError?: boolean;
}> {
const searchParams = new URLSearchParams();
if (params?.page) searchParams.set('page', params.page.toString());
if (params?.perPage) searchParams.set('per_page', params.perPage.toString());
if (params?.search) searchParams.set('search', params.search);
const queryString = searchParams.toString();
const result = await executeServerAction({
url: `${API_URL}/api/v1/bank-accounts${queryString ? `?${queryString}` : ''}`,
transform: (data: BankAccountPaginatedResponse) => ({
accounts: (data?.data || []).map(transformApiToFrontend),
meta: { currentPage: data?.current_page || 1, lastPage: data?.last_page || 1, perPage: data?.per_page || 20, total: data?.total || 0 },
}),
errorMessage: '계좌 목록 조회에 실패했습니다.',
});
return { success: result.success, data: result.data?.accounts, meta: result.data?.meta, error: result.error, __authError: result.__authError };
}
// ===== 계좌 상세 조회 =====
export async function getBankAccount(id: number): Promise<ActionResult<Account>> {
return executeServerAction({
url: `${API_URL}/api/v1/bank-accounts/${id}`,
transform: (data: BankAccountApiData) => transformApiToFrontend(data),
errorMessage: '계좌 조회에 실패했습니다.',
});
}
// ===== 계좌 생성 =====
export async function createBankAccount(data: AccountFormData): Promise<ActionResult<Account>> {
return executeServerAction({
url: `${API_URL}/api/v1/bank-accounts`,
method: 'POST',
body: transformFrontendToApi(data),
transform: (d: BankAccountApiData) => transformApiToFrontend(d),
errorMessage: '계좌 등록에 실패했습니다.',
});
}
// ===== 계좌 수정 =====
export async function updateBankAccount(id: number, data: Partial<AccountFormData>): Promise<ActionResult<Account>> {
return executeServerAction({
url: `${API_URL}/api/v1/bank-accounts/${id}`,
method: 'PUT',
body: transformFrontendToApi(data),
transform: (d: BankAccountApiData) => transformApiToFrontend(d),
errorMessage: '계좌 수정에 실패했습니다.',
});
}
// ===== 계좌 삭제 =====
export async function deleteBankAccount(id: number): Promise<ActionResult> {
return executeServerAction({
url: `${API_URL}/api/v1/bank-accounts/${id}`,
method: 'DELETE',
errorMessage: '계좌 삭제에 실패했습니다.',
});
}
// ===== 계좌 상태 토글 =====
export async function toggleBankAccountStatus(id: number): Promise<ActionResult<Account>> {
return executeServerAction({
url: `${API_URL}/api/v1/bank-accounts/${id}/toggle`,
method: 'PATCH',
transform: (data: BankAccountApiData) => transformApiToFrontend(data),
errorMessage: '상태 변경에 실패했습니다.',
});
}
// ===== 대표 계좌 설정 =====
export async function setPrimaryBankAccount(id: number): Promise<ActionResult<Account>> {
return executeServerAction({
url: `${API_URL}/api/v1/bank-accounts/${id}/set-primary`,
method: 'PATCH',
transform: (data: BankAccountApiData) => transformApiToFrontend(data),
errorMessage: '대표 계좌 설정에 실패했습니다.',
});
}
// ===== 다중 삭제 =====
export async function deleteBankAccounts(ids: number[]): Promise<{
success: boolean; deletedCount?: number; error?: string;
}> {
try {
const results = await Promise.all(ids.map(id => deleteBankAccount(id)));
const successCount = results.filter(r => r.success).length;
const failedCount = results.filter(r => !r.success).length;
if (failedCount > 0 && successCount === 0) {
return { success: false, error: '계좌 삭제에 실패했습니다.' };
}
if (failedCount > 0) {
return { success: true, deletedCount: successCount, error: `${failedCount}개의 계좌 삭제에 실패했습니다.` };
}
return { success: true, deletedCount: successCount };
} catch (error) {
if (isNextRedirectError(error)) throw error;
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}