fix(trade): use stream closed and trade_open instead of device clock
Changing the phone time can no longer open a product that the server has closed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,6 +10,7 @@ export interface Product {
|
||||
unit: string
|
||||
active: boolean
|
||||
show: boolean
|
||||
trade_open?: boolean
|
||||
show_in_assets?: boolean
|
||||
show_on_tv?: boolean
|
||||
open_sell: boolean
|
||||
|
||||
@@ -24,6 +24,19 @@ export function isProductTradeOpen(product: TradeOpenProduct, isBuy: boolean) {
|
||||
return isBuy ? isProductBuyOpen(product) : isProductSellOpen(product)
|
||||
}
|
||||
|
||||
/** ساعت کاری محصول از استریم (trade_open) — نه ساعت دستگاه */
|
||||
export function isProductTradingHoursOpen(
|
||||
product?: { show?: boolean; trade_open?: boolean } | null,
|
||||
) {
|
||||
if (!product) return false
|
||||
if (typeof product.trade_open === 'boolean') return product.trade_open
|
||||
return product.show === true
|
||||
}
|
||||
|
||||
export function productHoursClosedMessage() {
|
||||
return 'این محصول در حال حاضر خارج از ساعت کاری است.'
|
||||
}
|
||||
|
||||
/** تب اولیه با توجه به query و وضعیت باز/بسته خرید و فروش */
|
||||
export function resolveProductTradeIsBuy(product: TradeOpenProduct, routeState?: string | null) {
|
||||
const wantSell = routeState === 'sell'
|
||||
|
||||
+5
-14
@@ -225,20 +225,11 @@ export function isStoreClosedBySchedule(now = new Date()): boolean {
|
||||
return isOutsideTimeWindow(now, start, end)
|
||||
}
|
||||
|
||||
/** باز/بسته بودن فروشگاه فقط از استریم last.closed — نه ساعت دستگاه کاربر */
|
||||
export function isStoreClosed(): boolean {
|
||||
void productTradingHoursVersion.value
|
||||
const { closePriceEnabled } = getStoreTradingHours()
|
||||
|
||||
if (closePriceEnabled) {
|
||||
if (lastPrice.value?.closed === true) {
|
||||
return true
|
||||
}
|
||||
|
||||
return isStoreClosedBySchedule()
|
||||
}
|
||||
|
||||
// وقتی close_price خاموش است، صفر بودن قیمت مرجع به معنی بسته بودن فروشگاه نیست.
|
||||
return isStoreClosedBySchedule()
|
||||
void lastPriceReady.value
|
||||
void lastPrice.value
|
||||
return lastPrice.value?.closed === true
|
||||
}
|
||||
|
||||
export function getStoreClosedMessage(): string {
|
||||
@@ -306,7 +297,7 @@ export function getPriceDisplayState(): PriceDisplayState {
|
||||
return serverStatus
|
||||
}
|
||||
|
||||
if (isOutsideGlobalStoreHours()) {
|
||||
if (p?.closed === true) {
|
||||
if (!shouldShowHomePriceWhenClosed()) return 'closed'
|
||||
return hasShowableHomePrice(p) ? 'open' : 'no_quote'
|
||||
}
|
||||
|
||||
@@ -300,6 +300,8 @@ import {
|
||||
isProductBuyOpen,
|
||||
isProductSellOpen,
|
||||
isProductTradeOpen,
|
||||
isProductTradingHoursOpen,
|
||||
productHoursClosedMessage,
|
||||
productTradeClosedMessage,
|
||||
resolveProductTradeIsBuy,
|
||||
} from '@/utils/productTradeOpen'
|
||||
@@ -468,6 +470,7 @@ const priceUnavailable = computed(() => {
|
||||
const disableTransaction = computed(() =>
|
||||
loading.value ||
|
||||
isStoreClosed() ||
|
||||
!isProductTradingHoursOpen(props.ProductItem) ||
|
||||
!isProductTradeOpen(props.ProductItem, isBuy.value) ||
|
||||
form.value?.goldValue === undefined ||
|
||||
form.value?.goldValue === '' ||
|
||||
@@ -763,6 +766,10 @@ function okConfirm() {
|
||||
message.warning(getStoreClosedMessage())
|
||||
return
|
||||
}
|
||||
if (!isProductTradingHoursOpen(props.ProductItem)) {
|
||||
message.warning(productHoursClosedMessage())
|
||||
return
|
||||
}
|
||||
if (!isProductTradeOpen(props.ProductItem, isBuy.value)) {
|
||||
message.warning(productTradeClosedMessage(isBuy.value))
|
||||
return
|
||||
@@ -782,6 +789,10 @@ async function ok() {
|
||||
message.warning(getStoreClosedMessage())
|
||||
return
|
||||
}
|
||||
if (!isProductTradingHoursOpen(props.ProductItem)) {
|
||||
message.warning(productHoursClosedMessage())
|
||||
return
|
||||
}
|
||||
if (!isProductTradeOpen(props.ProductItem, isBuy.value)) {
|
||||
message.warning(productTradeClosedMessage(isBuy.value))
|
||||
return
|
||||
|
||||
@@ -274,6 +274,8 @@ import { SelectItem } from '@/models/SelectItem'
|
||||
import Bg_main from '@/components/bg_main.vue'
|
||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||
import { useCartStore } from '@/store/cartStore'
|
||||
import { getStoreClosedMessage, isStoreClosed } from '@/utils/storeHours'
|
||||
import { isProductTradingHoursOpen, productHoursClosedMessage } from '@/utils/productTradeOpen'
|
||||
|
||||
const typeDeal: SelectItem[] = [
|
||||
{
|
||||
@@ -346,6 +348,9 @@ const finalPrice = computed(() => (form.value?.goldValue * (isBuy.value ? props.
|
||||
const payablePrice = computed(() => finalPrice.value)
|
||||
|
||||
const disableTransaction = computed(() => {
|
||||
if (isStoreClosed() || !isProductTradingHoursOpen(props.ProductItem)) {
|
||||
return true
|
||||
}
|
||||
// If gateway is enabled, skip wallet balance checks
|
||||
if (isBuy.value && form.value?.gateway) {
|
||||
return (
|
||||
@@ -662,6 +667,14 @@ async function initPrice() {
|
||||
|
||||
async function okConfirm() {
|
||||
try {
|
||||
if (isStoreClosed()) {
|
||||
message.warning(getStoreClosedMessage())
|
||||
return
|
||||
}
|
||||
if (!isProductTradingHoursOpen(props.ProductItem)) {
|
||||
message.warning(productHoursClosedMessage())
|
||||
return
|
||||
}
|
||||
console.log('okConfirm called, form values:', form.value)
|
||||
console.log('disableTransaction:', disableTransaction.value)
|
||||
await refInput.value?.validateFields()
|
||||
@@ -703,6 +716,14 @@ async function okConfirm() {
|
||||
async function ok() {
|
||||
console.log('ok() function called')
|
||||
try {
|
||||
if (isStoreClosed()) {
|
||||
message.warning(getStoreClosedMessage())
|
||||
return
|
||||
}
|
||||
if (!isProductTradingHoursOpen(props.ProductItem)) {
|
||||
message.warning(productHoursClosedMessage())
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
infoVisible.value = true
|
||||
const data = await createOrder({
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="product-card relative flex cursor-pointer rounded-xl border border-gray-200 bg-white p-3 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg"
|
||||
:class="!props.ProductItem.show && 'opacity-50 grayscale'"
|
||||
:class="!isProductTradingHoursOpen(props.ProductItem) && 'opacity-50 grayscale'"
|
||||
>
|
||||
<span
|
||||
v-if="isOutOfStock"
|
||||
@@ -105,6 +105,7 @@ import { computed } from 'vue'
|
||||
import type { Product } from '@/models/products'
|
||||
import { usePrice } from '@/utils/usePrice'
|
||||
import { getProductBuyPrice } from '@/utils/productPrice'
|
||||
import { isProductTradingHoursOpen } from '@/utils/productTradeOpen'
|
||||
import ProductImage from '@/components/ProductImage.vue'
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="product-card relative flex h-full flex-col rounded-xl border border-gray-200 bg-white p-3 transition-all duration-300 hover:-translate-y-1 hover:shadow-lg cursor-pointer"
|
||||
:class="!props.ProductItem.show && 'opacity-50 grayscale'"
|
||||
:class="!isProductTradingHoursOpen(props.ProductItem) && 'opacity-50 grayscale'"
|
||||
>
|
||||
<span
|
||||
v-if="isOutOfStock"
|
||||
@@ -37,6 +37,7 @@ import { computed } from 'vue'
|
||||
import type { Product } from '@/models/products'
|
||||
import { usePrice } from '@/utils/usePrice'
|
||||
import { getProductBuyPrice } from '@/utils/productPrice'
|
||||
import { isProductTradingHoursOpen } from '@/utils/productTradeOpen'
|
||||
import ProductImage from '@/components/ProductImage.vue'
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -80,6 +80,7 @@ import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import ProductItemVertical from '@/views/base/products/components/ProductItemVertical.vue'
|
||||
import MobileProductsView from '@/views/base/products/mobile/MobileProductsView.vue'
|
||||
import { isProductVisible } from '@/utils/productPrice'
|
||||
import { isProductTradingHoursOpen } from '@/utils/productTradeOpen'
|
||||
import {
|
||||
productsActiveKey,
|
||||
productsFirstItems,
|
||||
@@ -102,6 +103,7 @@ const visibleTypeItems = computed(() => {
|
||||
})
|
||||
|
||||
function clickProduct(item: Product) {
|
||||
if (!isProductTradingHoursOpen(item)) return
|
||||
if (isProductVisible(item)) {
|
||||
router.push({ name: 'productDetail', params: { id: item.id } })
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { getProductById } from '@/api/products'
|
||||
import { findProductInCatalog, getProductsCatalog } from '@/composables/useProductsCatalog'
|
||||
import { findProductInCatalog, getProductsCatalog, getProductsCatalogRef } from '@/composables/useProductsCatalog'
|
||||
import { getLastPrices, stopPriceInterval } from '@/utils/price'
|
||||
import { getProductBuyPrice } from '@/utils/productPrice'
|
||||
import { isProductTradingHoursOpen } from '@/utils/productTradeOpen'
|
||||
import { loading } from '@/utils'
|
||||
import type { Product } from '@/models/products'
|
||||
|
||||
@@ -13,6 +14,7 @@ export interface ProductDetailPageContext {
|
||||
product: ReturnType<typeof ref<Product | undefined>>
|
||||
showFilter: ReturnType<typeof ref<boolean>>
|
||||
isOutOfStock: ReturnType<typeof computed<boolean>>
|
||||
hoursOpen: ReturnType<typeof computed<boolean>>
|
||||
handleBuyClick: () => void
|
||||
goToProduct: (id: number) => void
|
||||
getProductBuyPrice: typeof getProductBuyPrice
|
||||
@@ -33,6 +35,7 @@ export function useProductDetailPage() {
|
||||
})
|
||||
|
||||
const isOutOfStock = computed(() => !product.value?.inventory || product.value.inventory === 0)
|
||||
const hoursOpen = computed(() => isProductTradingHoursOpen(product.value))
|
||||
|
||||
watch(productId, (newId, oldId) => {
|
||||
if (newId && newId !== oldId) {
|
||||
@@ -41,6 +44,23 @@ export function useProductDetailPage() {
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => getProductsCatalogRef().value.map((p) => `${p.id}:${p.trade_open}:${p.show}`).join('|'),
|
||||
() => {
|
||||
const current = product.value
|
||||
if (!current?.id) return
|
||||
const live = findProductInCatalog(current.id)
|
||||
if (!live) return
|
||||
current.show = live.show
|
||||
if (typeof live.trade_open === 'boolean') {
|
||||
current.trade_open = live.trade_open
|
||||
}
|
||||
if (showFilter.value && !isProductTradingHoursOpen(current)) {
|
||||
showFilter.value = false
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
async function loadProduct() {
|
||||
try {
|
||||
loading.value = true
|
||||
@@ -67,7 +87,7 @@ export function useProductDetailPage() {
|
||||
}
|
||||
|
||||
function handleBuyClick() {
|
||||
if (!product.value?.show) return
|
||||
if (!isProductTradingHoursOpen(product.value)) return
|
||||
showFilter.value = true
|
||||
product.value = cloneDeep(product.value)
|
||||
}
|
||||
@@ -88,6 +108,7 @@ export function useProductDetailPage() {
|
||||
product,
|
||||
showFilter,
|
||||
isOutOfStock,
|
||||
hoursOpen,
|
||||
handleBuyClick,
|
||||
goToProduct,
|
||||
getProductBuyPrice,
|
||||
|
||||
@@ -18,14 +18,14 @@
|
||||
<div class="dproduct-info__price-row">
|
||||
<span class="dproduct-info__price">{{ usePrice(getProductBuyPrice(product)) }}</span>
|
||||
<BasicButton
|
||||
v-if="product.show && product.open_buy && product.inventory"
|
||||
v-if="hoursOpen && product.open_buy && product.inventory"
|
||||
type="primary"
|
||||
size="large"
|
||||
@click="handleBuyClick"
|
||||
>
|
||||
خرید محصول
|
||||
</BasicButton>
|
||||
<div v-else-if="!product.show" class="text-gray-500">این محصول در حال حاضر در دسترس نیست</div>
|
||||
<div v-else-if="!hoursOpen" class="text-gray-500">این محصول در حال حاضر در دسترس نیست</div>
|
||||
<div v-else-if="!product.open_buy" class="text-gray-500">خرید این محصول در حال حاضر امکان پذیر نیست</div>
|
||||
<div v-else class="text-gray-500">ناموجود</div>
|
||||
</div>
|
||||
@@ -72,7 +72,7 @@ import { loading } from '@/utils'
|
||||
import { usePrice } from '@/utils/usePrice'
|
||||
import { useProductDetailPageContext } from '../composables/useProductDetailPage'
|
||||
|
||||
const { product, isOutOfStock, handleBuyClick, goToProduct, getProductBuyPrice } = useProductDetailPageContext()
|
||||
const { product, isOutOfStock, hoursOpen, handleBuyClick, goToProduct, getProductBuyPrice } = useProductDetailPageContext()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<BasicButton
|
||||
v-if="product?.show && product?.open_buy && product?.inventory"
|
||||
v-if="hoursOpen && product?.open_buy && product?.inventory"
|
||||
class="w-full py-3"
|
||||
type="primary"
|
||||
size="large"
|
||||
@@ -25,7 +25,7 @@
|
||||
>
|
||||
خرید محصول
|
||||
</BasicButton>
|
||||
<div v-else-if="!product?.show" class="text-center text-gray-500 py-4">
|
||||
<div v-else-if="!hoursOpen" class="text-center text-gray-500 py-4">
|
||||
این محصول در حال حاضر در دسترس نیست
|
||||
</div>
|
||||
<div v-else-if="!product?.open_buy" class="text-center text-gray-500 py-4">
|
||||
@@ -76,7 +76,7 @@ import { Swiper, SwiperSlide } from 'swiper/vue'
|
||||
import 'swiper/swiper-bundle.css'
|
||||
import { useProductDetailPageContext } from '../composables/useProductDetailPage'
|
||||
|
||||
const { product, isOutOfStock, handleBuyClick, goToProduct, getProductBuyPrice } = useProductDetailPageContext()
|
||||
const { product, isOutOfStock, hoursOpen, handleBuyClick, goToProduct, getProductBuyPrice } = useProductDetailPageContext()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user