Refactor uploadBudget module

Refactor the uploadBudget.ts file into smaller, more manageable modules to improve code readability and maintainability.
This commit is contained in:
gpt-engineer-app[bot]
2025-03-21 12:40:06 +00:00
parent e03642a75e
commit 66c7b5f132
6 changed files with 347 additions and 191 deletions

View File

@@ -1,5 +1,20 @@
// 예산 동기화 관련 모듈 /**
* 예산 동기화 관련 모듈
* 모든 예산 관련 함수를 하나의 파일에서 내보냅니다.
*/
// 예산 업로드 관련 함수
export * from './uploadBudget'; export * from './uploadBudget';
export * from './uploadMonthlyBudget';
export * from './uploadCategoryBudgets';
// 예산 다운로드 관련 함수
export * from './downloadBudget'; export * from './downloadBudget';
// 예산 추적 관련 함수
export * from './modifiedBudgetsTracker'; export * from './modifiedBudgetsTracker';
// 타입 정의 및 유틸리티
export * from './types';
export * from './validators';

View File

@@ -0,0 +1,50 @@
/**
* 예산 동기화 관련 타입 정의
*/
// 예산 데이터 타입
export interface BudgetData {
daily: BudgetPeriod;
weekly: BudgetPeriod;
monthly: BudgetPeriod;
[key: string]: BudgetPeriod;
}
// 기간별 예산 타입
export interface BudgetPeriod {
targetAmount: number;
spentAmount: number;
remainingAmount: number;
[key: string]: number;
}
// 카테고리 예산 타입
export type CategoryBudgets = Record<string, number>;
// 예산 업로드 결과 타입
export interface BudgetUploadResult {
success: boolean;
message: string;
}
// 데이터베이스 예산 항목 타입
export interface BudgetRecord {
id?: string;
user_id: string;
month: number;
year: number;
total_budget: number;
created_at?: string;
updated_at: string;
}
// 데이터베이스 카테고리 예산 항목 타입
export interface CategoryBudgetRecord {
id?: string;
user_id: string;
category: string;
amount: number;
created_at?: string;
updated_at: string;
}

View File

@@ -1,10 +1,16 @@
import { supabase } from '@/lib/supabase'; /**
* 예산 데이터 업로드 메인 모듈
*/
import { isSyncEnabled } from '../syncSettings'; import { isSyncEnabled } from '../syncSettings';
import { import {
clearModifiedBudget, clearModifiedBudget,
clearModifiedCategoryBudgets clearModifiedCategoryBudgets
} from './modifiedBudgetsTracker'; } from './modifiedBudgetsTracker';
import { uploadMonthlyBudget } from './uploadMonthlyBudget';
import { uploadCategoryBudgets } from './uploadCategoryBudgets';
import { isValidMonthlyBudget, isValidCategoryBudgets } from './validators';
import { BudgetData, CategoryBudgets } from './types';
/** /**
* 예산 데이터를 서버에 업로드 * 예산 데이터를 서버에 업로드
@@ -20,16 +26,23 @@ export const uploadBudgets = async (userId: string): Promise<void> => {
// 예산 데이터 업로드 // 예산 데이터 업로드
if (budgetDataStr) { if (budgetDataStr) {
const budgetData = JSON.parse(budgetDataStr); try {
const budgetData: BudgetData = JSON.parse(budgetDataStr);
// 월간 예산이 0보다 클 때만 업로드 // 월간 예산이 유효할 때만 업로드
if (budgetData.monthly && typeof budgetData.monthly.targetAmount === 'number' && budgetData.monthly.targetAmount > 0) { if (isValidMonthlyBudget(budgetData)) {
console.log('유효한 월간 예산 발견:', budgetData.monthly.targetAmount); console.log('유효한 월간 예산 발견:', budgetData.monthly.targetAmount);
await uploadBudgetData(userId, budgetData); const uploadSuccess = await uploadMonthlyBudget(userId, budgetData);
// 업로드 성공 후 수정 추적 정보 초기화
clearModifiedBudget(); // 업로드 성공 후 수정 추적 정보 초기화
} else { if (uploadSuccess) {
console.log('월간 예산이 0 이하거나 없어서 업로드 건너뜀'); clearModifiedBudget();
}
} else {
console.log('월간 예산이 0 이하거나 없어서 업로드 건너뜀');
}
} catch (error) {
console.error('월간 예산 데이터 파싱 또는 업로드 오류:', error);
} }
} else { } else {
console.log('업로드할 예산 데이터가 없음'); console.log('업로드할 예산 데이터가 없음');
@@ -37,17 +50,23 @@ export const uploadBudgets = async (userId: string): Promise<void> => {
// 카테고리 예산 업로드 // 카테고리 예산 업로드
if (categoryBudgetsStr) { if (categoryBudgetsStr) {
const categoryBudgets = JSON.parse(categoryBudgetsStr); try {
const categoryBudgets: CategoryBudgets = JSON.parse(categoryBudgetsStr);
// 총 카테고리 예산이 0보다 클 때만 업로드 // 총 카테고리 예산이 유효할 때만 업로드
const totalCategoryBudget = Object.values(categoryBudgets).reduce((sum: number, val: number) => sum + val, 0); if (isValidCategoryBudgets(categoryBudgets)) {
if (totalCategoryBudget > 0) { console.log('유효한 카테고리 예산 발견');
console.log('유효한 카테고리 예산 발견:', totalCategoryBudget); const uploadSuccess = await uploadCategoryBudgets(userId, categoryBudgets);
await uploadCategoryBudgets(userId, categoryBudgets);
// 업로드 성공 후 수정 추적 정보 초기화 // 업로드 성공 후 수정 추적 정보 초기화
clearModifiedCategoryBudgets(); if (uploadSuccess) {
} else { clearModifiedCategoryBudgets();
console.log('카테고리 예산이 모두 0이어서 업로드 건너뜀'); }
} else {
console.log('카테고리 예산이 모두 0이어서 업로드 건너뜀');
}
} catch (error) {
console.error('카테고리 예산 데이터 파싱 또는 업로드 오류:', error);
} }
} else { } else {
console.log('업로드할 카테고리 예산이 없음'); console.log('업로드할 카테고리 예산이 없음');
@@ -59,171 +78,3 @@ export const uploadBudgets = async (userId: string): Promise<void> => {
throw error; throw error;
} }
}; };
/**
* 일반 예산 데이터 업로드
*/
async function uploadBudgetData(userId: string, parsedBudgetData: Record<string, any>): Promise<void> {
console.log('예산 데이터 업로드:', parsedBudgetData);
// 현재 월/년도 가져오기
const now = new Date();
const currentMonth = now.getMonth() + 1; // 0-11 -> 1-12
const currentYear = now.getFullYear();
// 기존 예산 데이터 확인
const { data: existingBudgets, error: fetchError } = await supabase
.from('budgets')
.select('*')
.eq('user_id', userId)
.eq('month', currentMonth)
.eq('year', currentYear);
if (fetchError) {
console.error('기존 예산 데이터 조회 실패:', fetchError);
throw fetchError;
}
// 월간 타겟 금액 가져오기
const monthlyTarget = parsedBudgetData.monthly.targetAmount;
// 예산이 0 이하면 업로드하지 않음
if (typeof monthlyTarget !== 'number' || monthlyTarget <= 0) {
console.log('월간 예산이 0 이하여서 업로드 건너뜀:', monthlyTarget);
return;
}
console.log('업로드할 월간 예산:', monthlyTarget);
// 현재 타임스탬프
const currentTimestamp = new Date().toISOString();
// 가능한 경우 서버 데이터와 비교하여 필요한 경우만 업데이트
if (existingBudgets && existingBudgets.length > 0) {
const existingBudget = existingBudgets[0];
// 새 예산이 기존 예산보다 클 때만 업데이트
if (typeof existingBudget.total_budget === 'number' && monthlyTarget > existingBudget.total_budget) {
console.log(`새 예산(${monthlyTarget})이 기존 예산(${existingBudget.total_budget})보다 큼, 업데이트 실행`);
// 기존 데이터 업데이트
const { error } = await supabase
.from('budgets')
.update({
total_budget: monthlyTarget,
updated_at: currentTimestamp
})
.eq('id', existingBudget.id);
if (error) {
console.error('예산 데이터 업데이트 실패:', error);
throw error;
}
console.log('예산 데이터 업데이트 성공');
} else {
console.log(`새 예산(${monthlyTarget})이 기존 예산(${existingBudget.total_budget})보다 작거나 같음, 업데이트 건너뜀`);
}
} else {
// 새 데이터 삽입
const { error } = await supabase
.from('budgets')
.insert({
user_id: userId,
month: currentMonth,
year: currentYear,
total_budget: monthlyTarget,
created_at: currentTimestamp,
updated_at: currentTimestamp
});
if (error) {
console.error('예산 데이터 삽입 실패:', error);
throw error;
}
console.log('예산 데이터 삽입 성공');
}
}
/**
* 카테고리 예산 데이터 업로드
*/
async function uploadCategoryBudgets(userId: string, parsedCategoryBudgets: Record<string, number>): Promise<void> {
console.log('카테고리 예산 업로드:', parsedCategoryBudgets);
// 기존 카테고리 예산 확인
const { data: existingCategoryBudgets, error: fetchError } = await supabase
.from('category_budgets')
.select('*')
.eq('user_id', userId);
if (fetchError) {
console.error('기존 카테고리 예산 조회 실패:', fetchError);
throw fetchError;
}
// 기존 카테고리 예산의 총액 계산
let existingTotal = 0;
if (existingCategoryBudgets && existingCategoryBudgets.length > 0) {
existingTotal = existingCategoryBudgets.reduce((sum, item) => {
return sum + (typeof item.amount === 'number' ? item.amount : 0);
}, 0);
}
// 새 카테고리 예산의 총액 계산
const newTotal = Object.values(parsedCategoryBudgets).reduce((sum: number, val: number) => sum + val, 0);
// 새 카테고리 예산 총액이 기존 카테고리 예산 총액보다 작거나 같으면 업로드 건너뜀
if (newTotal <= existingTotal && existingTotal > 0) {
console.log(`새 카테고리 예산 총액(${newTotal})이 기존 예산 총액(${existingTotal})보다 작거나 같음, 업로드 건너뜀`);
return;
}
// 새 카테고리 예산 총액이 0이면 업로드 건너뜀
if (newTotal <= 0) {
console.log('새 카테고리 예산 총액이 0이하여서 업로드 건너뜀');
return;
}
console.log(`새 카테고리 예산 총액(${newTotal})이 기존 예산 총액(${existingTotal})보다 큼, 업로드 실행`);
// 기존 카테고리 예산 삭제
const { error: deleteError } = await supabase
.from('category_budgets')
.delete()
.eq('user_id', userId);
if (deleteError) {
console.error('기존 카테고리 예산 삭제 실패:', deleteError);
// 오류가 나도 계속 진행 (중요 데이터가 아니기 때문)
}
// 현재 타임스탬프
const currentTimestamp = new Date().toISOString();
// 카테고리별 예산 데이터 변환 및 삽입
const categoryEntries = Object.entries(parsedCategoryBudgets)
.filter(([_, amount]) => typeof amount === 'number' && amount > 0) // 금액이 0보다 큰 것만 저장
.map(([category, amount]) => ({
user_id: userId,
category,
amount,
created_at: currentTimestamp,
updated_at: currentTimestamp
}));
if (categoryEntries.length > 0) {
const { error } = await supabase
.from('category_budgets')
.insert(categoryEntries);
if (error) {
console.error('카테고리 예산 삽입 실패:', error);
throw error;
}
console.log('카테고리 예산 삽입 성공:', categoryEntries.length, '개');
} else {
console.log('저장할 카테고리 예산이 없음');
}
}

View File

@@ -0,0 +1,92 @@
/**
* 카테고리 예산 업로드 기능
*/
import { supabase } from '@/lib/supabase';
import { CategoryBudgets, CategoryBudgetRecord } from './types';
import { calculateTotalCategoryBudget, filterValidCategoryBudgets } from './validators';
/**
* 카테고리 예산 데이터 업로드
*/
export async function uploadCategoryBudgets(userId: string, parsedCategoryBudgets: CategoryBudgets): Promise<boolean> {
console.log('카테고리 예산 업로드:', parsedCategoryBudgets);
// 기존 카테고리 예산 확인
const { data: existingCategoryBudgets, error: fetchError } = await supabase
.from('category_budgets')
.select('*')
.eq('user_id', userId);
if (fetchError) {
console.error('기존 카테고리 예산 조회 실패:', fetchError);
throw fetchError;
}
// 기존 카테고리 예산의 총액 계산
let existingTotal = 0;
if (existingCategoryBudgets && existingCategoryBudgets.length > 0) {
existingTotal = existingCategoryBudgets.reduce((sum, item) => {
return sum + (typeof item.amount === 'number' ? item.amount : 0);
}, 0);
}
// 새 카테고리 예산의 총액 계산
const newTotal = calculateTotalCategoryBudget(parsedCategoryBudgets);
// 새 카테고리 예산 총액이 기존 카테고리 예산 총액보다 작거나 같으면 업로드 건너뜀
if (newTotal <= existingTotal && existingTotal > 0) {
console.log(`새 카테고리 예산 총액(${newTotal})이 기존 예산 총액(${existingTotal})보다 작거나 같음, 업로드 건너뜀`);
return false;
}
// 새 카테고리 예산 총액이 0이면 업로드 건너뜀
if (newTotal <= 0) {
console.log('새 카테고리 예산 총액이 0이하여서 업로드 건너뜀');
return false;
}
console.log(`새 카테고리 예산 총액(${newTotal})이 기존 예산 총액(${existingTotal})보다 큼, 업로드 실행`);
// 기존 카테고리 예산 삭제
const { error: deleteError } = await supabase
.from('category_budgets')
.delete()
.eq('user_id', userId);
if (deleteError) {
console.error('기존 카테고리 예산 삭제 실패:', deleteError);
// 오류가 나도 계속 진행 (중요 데이터가 아니기 때문)
}
// 현재 타임스탬프
const currentTimestamp = new Date().toISOString();
// 카테고리별 예산 데이터 변환 및 삽입
const validCategoryBudgets = filterValidCategoryBudgets(parsedCategoryBudgets);
const categoryEntries: CategoryBudgetRecord[] = Object.entries(validCategoryBudgets)
.map(([category, amount]) => ({
user_id: userId,
category,
amount,
created_at: currentTimestamp,
updated_at: currentTimestamp
}));
if (categoryEntries.length > 0) {
const { error } = await supabase
.from('category_budgets')
.insert(categoryEntries);
if (error) {
console.error('카테고리 예산 삽입 실패:', error);
throw error;
}
console.log('카테고리 예산 삽입 성공:', categoryEntries.length, '개');
return true;
} else {
console.log('저장할 카테고리 예산이 없음');
return false;
}
}

View File

@@ -0,0 +1,98 @@
/**
* 월간 예산 업로드 기능
*/
import { supabase } from '@/lib/supabase';
import { BudgetData, BudgetRecord } from './types';
import { isValidMonthlyBudget } from './validators';
/**
* 월간 예산 데이터 업로드
*/
export async function uploadMonthlyBudget(userId: string, parsedBudgetData: BudgetData): Promise<boolean> {
console.log('월간 예산 데이터 업로드:', parsedBudgetData);
// 월간 타겟 금액 가져오기
const monthlyTarget = parsedBudgetData.monthly.targetAmount;
// 예산이 0 이하면 업로드하지 않음
if (typeof monthlyTarget !== 'number' || monthlyTarget <= 0) {
console.log('월간 예산이 0 이하여서 업로드 건너뜀:', monthlyTarget);
return false;
}
// 현재 월/년도 가져오기
const now = new Date();
const currentMonth = now.getMonth() + 1; // 0-11 -> 1-12
const currentYear = now.getFullYear();
// 기존 예산 데이터 확인
const { data: existingBudgets, error: fetchError } = await supabase
.from('budgets')
.select('*')
.eq('user_id', userId)
.eq('month', currentMonth)
.eq('year', currentYear);
if (fetchError) {
console.error('기존 예산 데이터 조회 실패:', fetchError);
throw fetchError;
}
console.log('업로드할 월간 예산:', monthlyTarget);
// 현재 타임스탬프
const currentTimestamp = new Date().toISOString();
// 가능한 경우 서버 데이터와 비교하여 필요한 경우만 업데이트
if (existingBudgets && existingBudgets.length > 0) {
const existingBudget = existingBudgets[0];
// 새 예산이 기존 예산보다 클 때만 업데이트
if (typeof existingBudget.total_budget === 'number' && monthlyTarget > existingBudget.total_budget) {
console.log(`새 예산(${monthlyTarget})이 기존 예산(${existingBudget.total_budget})보다 큼, 업데이트 실행`);
// 기존 데이터 업데이트
const { error } = await supabase
.from('budgets')
.update({
total_budget: monthlyTarget,
updated_at: currentTimestamp
})
.eq('id', existingBudget.id);
if (error) {
console.error('예산 데이터 업데이트 실패:', error);
throw error;
}
console.log('예산 데이터 업데이트 성공');
return true;
} else {
console.log(`새 예산(${monthlyTarget})이 기존 예산(${existingBudget.total_budget})보다 작거나 같음, 업데이트 건너뜀`);
return false;
}
} else {
// 새 데이터 삽입
const newBudget: BudgetRecord = {
user_id: userId,
month: currentMonth,
year: currentYear,
total_budget: monthlyTarget,
created_at: currentTimestamp,
updated_at: currentTimestamp
};
const { error } = await supabase
.from('budgets')
.insert(newBudget);
if (error) {
console.error('예산 데이터 삽입 실패:', error);
throw error;
}
console.log('예산 데이터 삽입 성공');
return true;
}
}

View File

@@ -0,0 +1,50 @@
/**
* 예산 데이터 검증 유틸리티
*/
import { BudgetData, CategoryBudgets } from './types';
/**
* 월간 예산이 유효한지 확인
*/
export const isValidMonthlyBudget = (budgetData: BudgetData | null): boolean => {
if (!budgetData || !budgetData.monthly) return false;
const { targetAmount } = budgetData.monthly;
return typeof targetAmount === 'number' && targetAmount > 0;
};
/**
* 카테고리 예산이 유효한지 확인
*/
export const isValidCategoryBudgets = (categoryBudgets: CategoryBudgets | null): boolean => {
if (!categoryBudgets) return false;
// 총 예산 금액 계산
const totalAmount = Object.values(categoryBudgets).reduce((sum, amount) => {
return sum + (typeof amount === 'number' ? amount : 0);
}, 0);
return totalAmount > 0;
};
/**
* 카테고리 예산 총액 계산
*/
export const calculateTotalCategoryBudget = (categoryBudgets: CategoryBudgets | null): number => {
if (!categoryBudgets) return 0;
return Object.values(categoryBudgets).reduce((sum, amount) => {
return sum + (typeof amount === 'number' ? amount : 0);
}, 0);
};
/**
* 유효한 카테고리 예산 항목만 필터링
*/
export const filterValidCategoryBudgets = (categoryBudgets: CategoryBudgets): CategoryBudgets => {
return Object.fromEntries(
Object.entries(categoryBudgets)
.filter(([_, amount]) => typeof amount === 'number' && amount > 0)
);
};