fix: 수동 매도 즉시 반영 + 매도/매수 결과 토스트 + 당일 매도대금 가용현금 반영
- 프론트: 토스트 알림 시스템(lib/toast + ToastHost) 추가, 매도/매수 성공·실패를 화면에 표시 (기존엔 console.log만, 실패는 블로킹 alert). 성공 토스트에 손익 표시. - 매도 직후 예수금/평가액 요약 즉시 갱신(apply_sell), 최근 5분 내 수동 매도 종목은 KIS 잔고 반영 지연으로 동기화가 되살리지 않도록 스킵(recent_sells). - 예수금이 D+2 결제 기준(dnca_tot_amt)이라 당일 매도대금이 화면·데이트레이딩 가용현금에 반영되지 않던 문제: 매수가능조회(inquire-psbl-order) ord_psbl_cash 를 우선 사용. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ import { useQueryClient } from '@tanstack/react-query'
|
||||
import { AxiosError } from 'axios'
|
||||
import api, { buyStockV2, sellStockV2, resetPendingTradesV2 } from '../lib/api'
|
||||
import { useTenantMode } from '../lib/tenant'
|
||||
import { toast } from '../lib/toast'
|
||||
|
||||
/** 계좌 미등록/미검증(400/423) 여부를 표시하는 확장 에러 타입 */
|
||||
export type TradeActionError = AxiosError<{ detail?: string }> & { isAccountSetupRequired?: boolean }
|
||||
@@ -58,11 +59,14 @@ export const useTradeActions = () => {
|
||||
// KIS 모의서버 지연 시 주문 1건에 30~60초 걸릴 수 있음 — 기본 15초로 끊으면
|
||||
// 서버는 체결하는데 화면은 실패로 보여 중복 주문을 유발함
|
||||
const body = { stock_code: stockCode, quantity, price: null }
|
||||
if (mode === 'v2') {
|
||||
await sellStockV2(body)
|
||||
} else {
|
||||
await api.post('/portfolio/sell', body, { timeout: 90000 })
|
||||
}
|
||||
const res = mode === 'v2'
|
||||
? await sellStockV2(body)
|
||||
: await api.post('/portfolio/sell', body, { timeout: 90000 })
|
||||
const d = (res as any)?.data ?? {}
|
||||
const pl = typeof d.profit_loss === 'number'
|
||||
? ` (손익 ${d.profit_loss >= 0 ? '+' : ''}${Math.round(d.profit_loss).toLocaleString('ko-KR')}원, ${(d.profit_loss_rate ?? 0) >= 0 ? '+' : ''}${Number(d.profit_loss_rate ?? 0).toFixed(2)}%)`
|
||||
: ''
|
||||
toast.success(`${d.message || `${stockName} ${quantity}주 매도 주문 완료`}${pl}`)
|
||||
|
||||
// Invalidate queries to refresh portfolio
|
||||
queryClient.invalidateQueries({
|
||||
@@ -79,6 +83,9 @@ export const useTradeActions = () => {
|
||||
`❌ Sell order failed for ${stockName}:`,
|
||||
err.response?.data?.detail || err.message
|
||||
)
|
||||
if (!err.isAccountSetupRequired) {
|
||||
toast.error(`${stockName} 매도 실패: ${err.response?.data?.detail || err.message}`)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
},
|
||||
@@ -91,11 +98,11 @@ export const useTradeActions = () => {
|
||||
if (mode === undefined) throw new TenantModeNotReadyError()
|
||||
try {
|
||||
const body = { stock_code: stockCode, stock_name: stockName, quantity: null, price: null }
|
||||
if (mode === 'v2') {
|
||||
await buyStockV2(body)
|
||||
} else {
|
||||
await api.post('/portfolio/buy', body, { timeout: 90000 })
|
||||
}
|
||||
const res = mode === 'v2'
|
||||
? await buyStockV2(body)
|
||||
: await api.post('/portfolio/buy', body, { timeout: 90000 })
|
||||
const d = (res as any)?.data ?? {}
|
||||
toast.success(d.message || `${stockName} 매수 주문 완료`)
|
||||
queryClient.invalidateQueries({ queryKey: ['portfolio-combined'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['trade-history'] })
|
||||
console.log(`✅ ${stockName} buy order completed`)
|
||||
@@ -105,6 +112,9 @@ export const useTradeActions = () => {
|
||||
`❌ Buy order failed for ${stockName}:`,
|
||||
err.response?.data?.detail || err.message
|
||||
)
|
||||
if (!err.isAccountSetupRequired) {
|
||||
toast.error(`${stockName} 매수 실패: ${err.response?.data?.detail || err.message}`)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user