- types/purchase: Purchase·PurchaseInsert에 quantity 필드 추가 - usePurchases: totalSpent·categoryBreakdown를 price×quantity 기준으로 변경 - extractErrorMessage 헬퍼 추가 (Supabase PostgrestError 메시지 정확히 추출) - pages/purchases/index: 수량 컬럼 추가, 가격 셀에 합계(단가×수량) 표시 - PurchaseForm: 수량 입력, 세미콜론(;) → ×1000 단가 변환, 합계 미리보기 - PurchaseExcelUpload: 수량 파싱·검증, 단가/수량/합계 컬럼 분리 - 카테고리 셀 → USelect 인라인 수정 및 즉시 재검증 - 템플릿에 수량 컬럼 추가 (단가 → 수량 순서) - 저장 실패 시 실제 오류 메시지 표시
159 lines
4.5 KiB
TypeScript
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
|
|
}
|
|
}
|