- executeServerAction 공통 유틸 도입으로 actions.ts 대폭 간소화 (50+개 파일) - sanitize 유틸 추가 (XSS 방지) - middleware CSP 헤더 추가 및 Open Redirect 방지 - 프록시 라우트 로깅 개발환경 한정으로 변경 - 프로덕션 불필요 console.log 제거 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
162 lines
5.6 KiB
TypeScript
162 lines
5.6 KiB
TypeScript
'use server';
|
|
|
|
|
|
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
|
import { cookies } from 'next/headers';
|
|
import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action';
|
|
import type { NoteReceivableItem, DailyAccountItem, MatchStatus } from './types';
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
|
|
|
// ===== API 응답 타입 =====
|
|
interface NoteReceivableItemApi {
|
|
id: string;
|
|
content: string;
|
|
current_balance: number;
|
|
issue_date: string;
|
|
due_date: string;
|
|
}
|
|
|
|
interface DailyAccountItemApi {
|
|
id: string;
|
|
category: string;
|
|
match_status: MatchStatus;
|
|
carryover: number;
|
|
income: number;
|
|
expense: number;
|
|
balance: number;
|
|
currency: 'KRW' | 'USD';
|
|
}
|
|
|
|
interface DailyReportSummaryApi {
|
|
date: string;
|
|
day_of_week: string;
|
|
note_receivable_total: number;
|
|
foreign_currency_total: number;
|
|
cash_asset_total: number;
|
|
krw_totals: { carryover: number; income: number; expense: number; balance: number };
|
|
usd_totals: { carryover: number; income: number; expense: number; balance: number };
|
|
}
|
|
|
|
// ===== API → Frontend 변환 =====
|
|
function transformNoteReceivable(item: NoteReceivableItemApi): NoteReceivableItem {
|
|
return {
|
|
id: item.id, content: item.content, currentBalance: item.current_balance,
|
|
issueDate: item.issue_date, dueDate: item.due_date,
|
|
};
|
|
}
|
|
|
|
function transformDailyAccount(item: DailyAccountItemApi): DailyAccountItem {
|
|
return {
|
|
id: item.id, category: item.category, matchStatus: item.match_status,
|
|
carryover: item.carryover, income: item.income, expense: item.expense,
|
|
balance: item.balance, currency: item.currency,
|
|
};
|
|
}
|
|
|
|
// ===== 어음 및 외상매출채권 현황 조회 =====
|
|
export async function getNoteReceivables(params?: {
|
|
date?: string;
|
|
}): Promise<{ success: boolean; data: NoteReceivableItem[]; error?: string }> {
|
|
const searchParams = new URLSearchParams();
|
|
if (params?.date) searchParams.set('date', params.date);
|
|
const queryString = searchParams.toString();
|
|
|
|
const result = await executeServerAction({
|
|
url: `${API_URL}/api/v1/daily-report/note-receivables${queryString ? `?${queryString}` : ''}`,
|
|
transform: (data: NoteReceivableItemApi[]) => (data || []).map(transformNoteReceivable),
|
|
errorMessage: '어음 현황 조회에 실패했습니다.',
|
|
});
|
|
return { success: result.success, data: result.data || [], error: result.error };
|
|
}
|
|
|
|
// ===== 일별 계좌 현황 조회 =====
|
|
export async function getDailyAccounts(params?: {
|
|
date?: string;
|
|
}): Promise<{ success: boolean; data: DailyAccountItem[]; error?: string }> {
|
|
const searchParams = new URLSearchParams();
|
|
if (params?.date) searchParams.set('date', params.date);
|
|
const queryString = searchParams.toString();
|
|
|
|
const result = await executeServerAction({
|
|
url: `${API_URL}/api/v1/daily-report/daily-accounts${queryString ? `?${queryString}` : ''}`,
|
|
transform: (data: DailyAccountItemApi[]) => (data || []).map(transformDailyAccount),
|
|
errorMessage: '계좌 현황 조회에 실패했습니다.',
|
|
});
|
|
return { success: result.success, data: result.data || [], error: result.error };
|
|
}
|
|
|
|
// ===== 일일 보고서 요약 조회 =====
|
|
export async function getDailyReportSummary(params?: {
|
|
date?: string;
|
|
}): Promise<ActionResult<{
|
|
date: string;
|
|
dayOfWeek: string;
|
|
noteReceivableTotal: number;
|
|
foreignCurrencyTotal: number;
|
|
cashAssetTotal: number;
|
|
krwTotals: { carryover: number; income: number; expense: number; balance: number };
|
|
usdTotals: { carryover: number; income: number; expense: number; balance: number };
|
|
}>> {
|
|
const searchParams = new URLSearchParams();
|
|
if (params?.date) searchParams.set('date', params.date);
|
|
const queryString = searchParams.toString();
|
|
|
|
return executeServerAction({
|
|
url: `${API_URL}/api/v1/daily-report/summary${queryString ? `?${queryString}` : ''}`,
|
|
transform: (data: DailyReportSummaryApi) => ({
|
|
date: data.date,
|
|
dayOfWeek: data.day_of_week,
|
|
noteReceivableTotal: data.note_receivable_total,
|
|
foreignCurrencyTotal: data.foreign_currency_total,
|
|
cashAssetTotal: data.cash_asset_total,
|
|
krwTotals: data.krw_totals,
|
|
usdTotals: data.usd_totals,
|
|
}),
|
|
errorMessage: '요약 조회에 실패했습니다.',
|
|
});
|
|
}
|
|
|
|
// ===== 일일 보고서 엑셀 다운로드 =====
|
|
export async function exportDailyReportExcel(params?: {
|
|
date?: string;
|
|
}): Promise<{
|
|
success: boolean;
|
|
data?: Blob;
|
|
filename?: string;
|
|
error?: string;
|
|
}> {
|
|
try {
|
|
const cookieStore = await cookies();
|
|
const token = cookieStore.get('access_token')?.value;
|
|
|
|
const headers: HeadersInit = {
|
|
'Accept': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
'Authorization': token ? `Bearer ${token}` : '',
|
|
'X-API-KEY': process.env.API_KEY || '',
|
|
};
|
|
|
|
const searchParams = new URLSearchParams();
|
|
if (params?.date) searchParams.set('date', params.date);
|
|
const queryString = searchParams.toString();
|
|
|
|
const response = await fetch(
|
|
`${API_URL}/api/v1/daily-report/export${queryString ? `?${queryString}` : ''}`,
|
|
{ method: 'GET', headers }
|
|
);
|
|
|
|
if (!response.ok) {
|
|
return { success: false, error: `API 오류: ${response.status}` };
|
|
}
|
|
|
|
const blob = await response.blob();
|
|
const contentDisposition = response.headers.get('Content-Disposition');
|
|
const filename = contentDisposition?.match(/filename="?(.+)"?/)?.[1] || `일일일보_${params?.date || 'today'}.xlsx`;
|
|
|
|
return { success: true, data: blob, filename };
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('[DailyReportActions] exportDailyReportExcel error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
} |