feat(trade): wait on colleague order until status is final
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+11
@@ -104,6 +104,7 @@
|
||||
</div>
|
||||
|
||||
<GoldDayCalculatorModal />
|
||||
<GoldLoading v-model:show="colleagueOrderWaiting" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -129,6 +130,8 @@ import EnamadBadge from '@/components/EnamadBadge.vue'
|
||||
import { usePwaInstall } from '@/composables/usePwaInstall'
|
||||
import PwaInstallGuideModal from '@/components/pwa/PwaInstallGuideModal.vue'
|
||||
import GoldDayCalculatorModal from '@/components/GoldDayCalculatorModal.vue'
|
||||
import GoldLoading from '@/components/GoldLoading.vue'
|
||||
import { colleagueOrderWaiting, resumeColleagueOrderIfPending } from '@/composables/useColleagueOrderWait'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
import { connectChatIfExists, disconnectChat, isSettingFlagOn, unreadSupportCount, useChatService } from '@/utils/chat'
|
||||
@@ -272,6 +275,14 @@ watch(
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
() => getToken(),
|
||||
(token) => {
|
||||
if (token) void resumeColleagueOrderIfPending()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(latestIncomingNotification, (payload) => {
|
||||
if (!payload) return
|
||||
if (payload.at === lastSupportToastAt) return
|
||||
|
||||
+14
-3
@@ -7,11 +7,22 @@ import {useUserStore} from "@/store/userStore";
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
export const createOrder = async (obj: Order): Promise<any> => {
|
||||
return await useAxios.post<Order>(`/customer${!userStore.isSingle ? '' : '/single'}/order`, obj)
|
||||
function customerOrderPrefix() {
|
||||
return `/customer${!userStore.isSingle ? '' : '/single'}`
|
||||
}
|
||||
|
||||
export const createOrder = async (obj: Order): Promise<any> => {
|
||||
return await useAxios.post<Order>(`${customerOrderPrefix()}/order`, obj)
|
||||
}
|
||||
|
||||
export const getPendingColleagueOrder = async (): Promise<any> => {
|
||||
return await useAxios.get(`${customerOrderPrefix()}/order/pending`)
|
||||
}
|
||||
|
||||
export const getColleagueOrderStatus = async (id: number): Promise<any> => {
|
||||
return await useAxios.get(`${customerOrderPrefix()}/order/${id}/status`)
|
||||
}
|
||||
|
||||
export const getOrders = async (tbl: Ref<UnwrapRef<TableInput>>): Promise<TableOutput<Transaction>> => {
|
||||
return await useAxios.post<TableOutput<Order>>(`/customer${!userStore.isSingle ? '' : '/single'}/order/table`, tbl)
|
||||
return await useAxios.post<TableOutput<Order>>(`${customerOrderPrefix()}/order/table`, tbl)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import { ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import router from '@/router'
|
||||
import { getColleagueOrderStatus, getPendingColleagueOrder } from '@/api/order'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { useWalletStore } from '@/store/walletStore'
|
||||
import { useFundUserStore } from '@/store/fundStore'
|
||||
import { getToken } from '@/utils'
|
||||
|
||||
export const colleagueOrderWaiting = ref(false)
|
||||
|
||||
const CLIENT_POLL_MS = 5000
|
||||
const STREAM_EVENT = 'colleague-order'
|
||||
|
||||
type WaitResult = 'success' | 'failed' | 'none'
|
||||
|
||||
let inflight: Promise<WaitResult> | null = null
|
||||
|
||||
export function emitColleagueOrderFromStream(payload: unknown) {
|
||||
window.dispatchEvent(new CustomEvent(STREAM_EVENT, { detail: payload }))
|
||||
}
|
||||
|
||||
export function isColleagueOrderPending(res: unknown): boolean {
|
||||
if (!res || typeof res !== 'object') return false
|
||||
const row = res as Record<string, any>
|
||||
if (row.pending === true) return true
|
||||
if (Array.isArray(row) && row[0]?.pending === true) return true
|
||||
const sts = Number(
|
||||
row[0]?.sts ?? row.sts ?? row[1]?.order?.sts ?? row.order?.sts ?? NaN
|
||||
)
|
||||
return sts === 4
|
||||
}
|
||||
|
||||
export function colleagueOrderIdFromResponse(res: unknown): number | null {
|
||||
if (!res || typeof res !== 'object') return null
|
||||
const row = res as Record<string, any>
|
||||
const id = Number(
|
||||
row[0]?.order_id ?? row.order_id ?? row[1]?.order?.id ?? row.order?.id ?? NaN
|
||||
)
|
||||
return id > 0 ? id : null
|
||||
}
|
||||
|
||||
async function finishSuccess() {
|
||||
try {
|
||||
await useUserStore().getMyInfo()
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
try {
|
||||
await useWalletStore().getMyWallet()
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
try {
|
||||
await useFundUserStore().getMyFund()
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
try {
|
||||
const { resetTransactionsPageData } = await import(
|
||||
'@/views/base/transactions/composables/useTransactionsPageData'
|
||||
)
|
||||
resetTransactionsPageData()
|
||||
} catch {
|
||||
/* optional */
|
||||
}
|
||||
message.success('سفارش شما با موفقیت انجام شد')
|
||||
if (router.currentRoute.value.name !== 'transactions') {
|
||||
await router.push({ name: 'transactions' })
|
||||
}
|
||||
}
|
||||
|
||||
function stsFromPayload(payload: Record<string, any> | null | undefined): number | null {
|
||||
if (!payload) return null
|
||||
const sts = Number(payload.sts ?? payload.order?.sts)
|
||||
return Number.isFinite(sts) ? sts : null
|
||||
}
|
||||
|
||||
async function readStatus(orderId: number | null): Promise<number | null> {
|
||||
try {
|
||||
if (orderId && orderId > 0) {
|
||||
const row = (await getColleagueOrderStatus(orderId)) as Record<string, any>
|
||||
return stsFromPayload(row)
|
||||
}
|
||||
const row = (await getPendingColleagueOrder()) as Record<string, any>
|
||||
if (row?.pending === false && row?.sts == null) {
|
||||
return null
|
||||
}
|
||||
return stsFromPayload(row)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function waitForColleagueOrder(orderId?: number | null): Promise<WaitResult> {
|
||||
if (inflight) return inflight
|
||||
|
||||
inflight = (async () => {
|
||||
colleagueOrderWaiting.value = true
|
||||
let settled: WaitResult | null = null
|
||||
|
||||
const finish = (result: WaitResult) => {
|
||||
if (settled) return
|
||||
settled = result
|
||||
}
|
||||
|
||||
const onStream = (event: Event) => {
|
||||
const detail = (event as CustomEvent).detail as Record<string, any> | undefined
|
||||
if (orderId && Number(detail?.order_id) > 0 && Number(detail.order_id) !== orderId) {
|
||||
return
|
||||
}
|
||||
const sts = stsFromPayload(detail)
|
||||
if (sts === 2) finish('success')
|
||||
if (sts === 3) finish('failed')
|
||||
}
|
||||
|
||||
window.addEventListener(STREAM_EVENT, onStream)
|
||||
|
||||
try {
|
||||
while (!settled) {
|
||||
const sts = await readStatus(orderId ?? null)
|
||||
if (sts === 2) {
|
||||
finish('success')
|
||||
break
|
||||
}
|
||||
if (sts === 3) {
|
||||
finish('failed')
|
||||
break
|
||||
}
|
||||
await new Promise<void>((resolve) => {
|
||||
const timer = window.setTimeout(resolve, CLIENT_POLL_MS)
|
||||
const once = () => {
|
||||
window.clearTimeout(timer)
|
||||
resolve()
|
||||
}
|
||||
window.addEventListener(STREAM_EVENT, once, { once: true })
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
window.removeEventListener(STREAM_EVENT, onStream)
|
||||
colleagueOrderWaiting.value = false
|
||||
}
|
||||
|
||||
if (settled === 'success') {
|
||||
await finishSuccess()
|
||||
return 'success'
|
||||
}
|
||||
|
||||
message.error('سفارش توسط همکار رد شد')
|
||||
return 'failed'
|
||||
})().finally(() => {
|
||||
inflight = null
|
||||
})
|
||||
|
||||
return inflight
|
||||
}
|
||||
|
||||
export async function afterCreateOrder(res: unknown): Promise<WaitResult> {
|
||||
if (isColleagueOrderPending(res)) {
|
||||
return waitForColleagueOrder(colleagueOrderIdFromResponse(res))
|
||||
}
|
||||
await finishSuccess()
|
||||
return 'success'
|
||||
}
|
||||
|
||||
export async function resumeColleagueOrderIfPending(): Promise<WaitResult> {
|
||||
if (!getToken()) return 'none'
|
||||
try {
|
||||
const row = (await getPendingColleagueOrder()) as Record<string, any>
|
||||
if (!row?.pending) return 'none'
|
||||
const id = Number(row.order_id ?? row.order?.id ?? 0)
|
||||
return waitForColleagueOrder(id > 0 ? id : null)
|
||||
} catch {
|
||||
return 'none'
|
||||
}
|
||||
}
|
||||
@@ -560,6 +560,16 @@ function bindStreamEvents(source: AuthPriceStream) {
|
||||
}
|
||||
})
|
||||
|
||||
source.addEventListener('colleague_order', (event) => {
|
||||
try {
|
||||
void import('@/composables/useColleagueOrderWait').then(({ emitColleagueOrderFromStream }) => {
|
||||
emitColleagueOrderFromStream(JSON.parse(event.data))
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn('Colleague order SSE parse failed', error)
|
||||
}
|
||||
})
|
||||
|
||||
source.addEventListener('ping', () => {
|
||||
touchStreamActivity()
|
||||
})
|
||||
|
||||
@@ -251,6 +251,7 @@ import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import router from '@/router'
|
||||
import { useWalletStore } from '@/store/walletStore'
|
||||
import { createOrder } from '@/api/order'
|
||||
import { afterCreateOrder } from '@/composables/useColleagueOrderWait'
|
||||
import { message } from 'ant-design-vue'
|
||||
import type { Price } from '@/models/home'
|
||||
import { getLastPrice } from '@/api/home'
|
||||
@@ -790,7 +791,7 @@ async function ok() {
|
||||
loading.value = true
|
||||
await nextTick()
|
||||
infoVisible.value = true
|
||||
await createOrder({
|
||||
const res = await createOrder({
|
||||
direction: isBuy.value ? 1 : 2,
|
||||
price: Math.round(unitPrice.value),
|
||||
amount: normalizeOrderGoldAmount(orderGoldAmount.value),
|
||||
@@ -798,17 +799,16 @@ async function ok() {
|
||||
target_cash: calcTradeTargetCash(form.value.goldCash, inputLock.value),
|
||||
abshode_id: props.ProductItem?.id,
|
||||
})
|
||||
await walletStore.getMyWallet()
|
||||
await loadWalletAssets(true)
|
||||
await router.push({ name: 'transactions' })
|
||||
const result = await afterCreateOrder(res)
|
||||
if (result !== 'success') {
|
||||
return
|
||||
}
|
||||
form.value = {
|
||||
goldValue: undefined,
|
||||
goldCash: undefined,
|
||||
percentWallet: 0,
|
||||
}
|
||||
inputLock.value = null
|
||||
message.success('سفارش شما با موفقیت انجام شد')
|
||||
await fundStore.getMyFund()
|
||||
} catch (e) {
|
||||
// console.log(e)
|
||||
// await initPrice()
|
||||
|
||||
@@ -252,6 +252,7 @@ import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import router from '@/router'
|
||||
import { useWalletStore } from '@/store/walletStore'
|
||||
import { createOrder } from '@/api/order'
|
||||
import { afterCreateOrder } from '@/composables/useColleagueOrderWait'
|
||||
import { FormInstance, message } from 'ant-design-vue'
|
||||
import type { Price } from '@/models/home'
|
||||
import Icon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
@@ -734,9 +735,10 @@ async function ok() {
|
||||
window.location.href = returnUrl
|
||||
return // Stop execution here
|
||||
}
|
||||
// If no gateway, continue with normal flow
|
||||
await walletStore.getMyWallet()
|
||||
await router.push({ name: 'transactions' })
|
||||
const result = await afterCreateOrder(data)
|
||||
if (result !== 'success') {
|
||||
return
|
||||
}
|
||||
form.value = {
|
||||
goldValue: undefined,
|
||||
goldCash: undefined,
|
||||
@@ -744,9 +746,6 @@ async function ok() {
|
||||
method: 'cash',
|
||||
gateway: false
|
||||
}
|
||||
// refreshTable()
|
||||
message.success('سفارش شما با موفقیت انجام شد')
|
||||
await loadWalletAssets(true)
|
||||
} catch (e) {
|
||||
// console.log(e)
|
||||
// await initPrice()
|
||||
|
||||
Reference in New Issue
Block a user