Files
nuxt-claude/app/composables/usePurchases.ts
hyeonggil 9ade6abf4c feat: 구매 관리 수량 필드 추가 및 엑셀 업로드 개선
- types/purchase: Purchase·PurchaseInsert에 quantity 필드 추가
- usePurchases: totalSpent·categoryBreakdown를 price×quantity 기준으로 변경
  - extractErrorMessage 헬퍼 추가 (Supabase PostgrestError 메시지 정확히 추출)
- pages/purchases/index: 수량 컬럼 추가, 가격 셀에 합계(단가×수량) 표시
- PurchaseForm: 수량 입력, 세미콜론(;) → ×1000 단가 변환, 합계 미리보기
- PurchaseExcelUpload: 수량 파싱·검증, 단가/수량/합계 컬럼 분리
  - 카테고리 셀 → USelect 인라인 수정 및 즉시 재검증
  - 템플릿에 수량 컬럼 추가 (단가 → 수량 순서)
  - 저장 실패 시 실제 오류 메시지 표시
2026-03-08 23:01:27 +09:00

159 lines
4.5 KiB
TypeScript

import type { Purchase, PurchaseInsert, EquipmentCategory } from '~/types/purchase'
// Supabase PostgrestError는 instanceof Error가 아니므로 message 프로퍼티를 직접 추출
function extractErrorMessage(e: unknown, fallback: string): string {
if (e instanceof Error) return e.message
if (e && typeof e === 'object' && 'message' in e) return String((e as { message: unknown }).message)
return fallback
}
export function usePurchases() {
const client = useSupabaseClient()
const user = useSupabaseUser()
const purchases = ref<Purchase[]>([])
const loading = ref(false)
const error = ref<string | null>(null)
const totalSpent = computed(() =>
purchases.value.reduce((sum, p) => sum + p.price * (p.quantity ?? 1), 0)
)
const categoryBreakdown = computed(() => {
const breakdown: Record<string, number> = {}
for (const p of purchases.value) {
breakdown[p.category] = (breakdown[p.category] ?? 0) + p.price * (p.quantity ?? 1)
}
return breakdown
})
async function fetchPurchases() {
if (!user.value) return
loading.value = true
error.value = null
try {
const { data, error: err } = await client
.from('purchases')
.select('*')
.eq('user_id', user.value.id)
.order('purchase_date', { ascending: false })
if (err) throw err
purchases.value = data as Purchase[]
} catch (e: unknown) {
error.value = extractErrorMessage(e, '오류가 발생했습니다')
} finally {
loading.value = false
}
}
async function createPurchase(payload: PurchaseInsert) {
if (!user.value) return
loading.value = true
error.value = null
try {
const { data, error: err } = await client
.from('purchases')
.insert({ ...payload, user_id: user.value.id })
.select()
.single()
if (err) throw err
purchases.value.unshift(data as Purchase)
return data as Purchase
} catch (e: unknown) {
error.value = extractErrorMessage(e, '저장에 실패했습니다')
} finally {
loading.value = false
}
}
async function updatePurchase(id: string, payload: Partial<PurchaseInsert>) {
if (!user.value) return
loading.value = true
error.value = null
try {
const { data, error: err } = await client
.from('purchases')
.update(payload)
.eq('id', id)
.eq('user_id', user.value.id)
.select()
.single()
if (err) throw err
const idx = purchases.value.findIndex(p => p.id === id)
if (idx !== -1) purchases.value[idx] = data as Purchase
return data as Purchase
} catch (e: unknown) {
error.value = extractErrorMessage(e, '수정에 실패했습니다')
} finally {
loading.value = false
}
}
async function deletePurchase(id: string) {
if (!user.value) return
loading.value = true
error.value = null
try {
const { error: err } = await client
.from('purchases')
.delete()
.eq('id', id)
.eq('user_id', user.value.id)
if (err) throw err
purchases.value = purchases.value.filter(p => p.id !== id)
} catch (e: unknown) {
error.value = extractErrorMessage(e, '삭제에 실패했습니다')
} finally {
loading.value = false
}
}
async function getPurchase(id: string): Promise<Purchase | null> {
if (!user.value) return null
try {
const { data, error: err } = await client
.from('purchases')
.select('*')
.eq('id', id)
.eq('user_id', user.value.id)
.single()
if (err) throw err
return data as Purchase
} catch {
return null
}
}
async function bulkCreatePurchases(payloads: PurchaseInsert[]): Promise<number> {
if (!user.value || payloads.length === 0) return 0
loading.value = true
error.value = null
try {
const rows = payloads.map(p => ({ ...p, user_id: user.value!.id }))
const { data, error: err } = await client.from('purchases').insert(rows).select()
if (err) throw err
purchases.value.unshift(...(data as Purchase[]))
return data.length
} catch (e: unknown) {
error.value = extractErrorMessage(e, '일괄 저장에 실패했습니다')
return 0
} finally {
loading.value = false
}
}
return {
purchases: readonly(purchases),
loading: readonly(loading),
error: readonly(error),
totalSpent,
categoryBreakdown,
fetchPurchases,
createPurchase,
updatePurchase,
deletePurchase,
getPurchase,
bulkCreatePurchases
}
}