fix bugs of physicalRecieve & login-code-input & odometer-digit
This commit is contained in:
@@ -36,11 +36,10 @@
|
||||
/>
|
||||
<span class="price-change-badge__value">{{ formatSignedPercentChange(percentChange) }}</span>
|
||||
</div>
|
||||
<Vue3Odometer
|
||||
:value="displayGoldVal"
|
||||
<span
|
||||
class="o-text !text-xl font-bold price-odometer--boursecu"
|
||||
style="direction: ltr"
|
||||
/>
|
||||
>{{ usePriceWithoutType(displayGoldVal) }}</span>
|
||||
<span class="price-unit--boursecu">ریال</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,9 +58,7 @@ import {
|
||||
getPriceDisplayState,
|
||||
getStoreClosedHint,
|
||||
} from '@/utils/storeHours'
|
||||
import Vue3Odometer from 'vue3-odometer'
|
||||
import 'odometer/themes/odometer-theme-default.css'
|
||||
import { formatSignedPercentChange } from '@/utils/usePrice'
|
||||
import { formatSignedPercentChange, usePriceWithoutType } from '@/utils/usePrice'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
/>
|
||||
<span class="dhome-hero-price__delta-value">{{ formatSignedPercentChange(percentChange) }}</span>
|
||||
</span>
|
||||
<Vue3Odometer
|
||||
:value="displayGoldVal"
|
||||
<span
|
||||
class="dhome-hero-price__odometer o-text dhome-hero-price__odometer--boursecu"
|
||||
/>
|
||||
style="direction: ltr"
|
||||
>{{ usePriceWithoutType(displayGoldVal) }}</span>
|
||||
<span class="dhome-hero-price__unit dhome-hero-price__unit--boursecu">ریال</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -48,8 +48,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import Vue3Odometer from 'vue3-odometer'
|
||||
import 'odometer/themes/odometer-theme-default.css'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { getTradeBuyPrice, lastPrice, percentPriceChanged } from '@/utils/price'
|
||||
@@ -59,7 +57,7 @@ import {
|
||||
getPriceDisplayState,
|
||||
getStoreClosedHint,
|
||||
} from '@/utils/storeHours'
|
||||
import { formatSignedPercentChange } from '@/utils/usePrice'
|
||||
import { formatSignedPercentChange, usePriceWithoutType } from '@/utils/usePrice'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
/>
|
||||
<div class="pr-cart-row__stock text-xs">
|
||||
<span>مقدار موجودی</span>
|
||||
<span class="font-bold">{{ item.product.wallet.gold }}</span>
|
||||
<span>{{ getProductQuantityUnit(item.product) }}</span>
|
||||
<span class="font-bold">{{ displayGold }}</span>
|
||||
<span>{{ quantityUnit }}</span>
|
||||
<div
|
||||
v-if="getProductUnitWeightLabel(item.product)"
|
||||
class="mt-0.5 text-xs text-gray-500"
|
||||
@@ -26,34 +26,42 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 flex justify-between items-center gap-2">
|
||||
<div class="shrink-0">تعداد درخواستی:</div>
|
||||
<div class="shrink-0">{{ isCountBased ? 'تعداد درخواستی:' : 'مقدار درخواستی:' }}</div>
|
||||
<div
|
||||
class="flex items-center gap-1 "
|
||||
class="flex items-center gap-1"
|
||||
:class="variant === 'desktop' ? 'dpr-cart-row__input' : 'pr-cart-row__input'"
|
||||
>
|
||||
<BasicInput
|
||||
v-model:value="item.count"
|
||||
:addon-before="getProductQuantityUnit(item.product)"
|
||||
:addon-before="quantityUnit"
|
||||
:addon-bg-color="config.config?.color"
|
||||
addon-text-color="white"
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
:inputmode="isCountBased ? 'numeric' : 'decimal'"
|
||||
@update:value="onCountInput"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="countError" class="mt-1 text-xs text-red-600">{{ countError }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
|
||||
import { useConfigStore } from '@/store/configStore'
|
||||
import type { CountPInventory } from '@/models/products'
|
||||
import { getProductDisplayImage } from '@/utils/productAssets'
|
||||
import { getProductQuantityUnit, getProductUnitWeightLabel } from '@/utils/productDealMethod'
|
||||
import {
|
||||
getProductQuantityUnit,
|
||||
getProductUnitWeightLabel,
|
||||
isCountBasedProduct,
|
||||
} from '@/utils/productDealMethod'
|
||||
import { formatGoldAmount, usePriceWithoutType } from '@/utils/usePrice'
|
||||
import { persianToEnglishDigits } from '@/utils/persianDigits'
|
||||
|
||||
interface Props {
|
||||
item: CountPInventory
|
||||
@@ -64,6 +72,10 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
variant: 'mobile',
|
||||
})
|
||||
|
||||
const countError = ref('')
|
||||
const isCountBased = computed(() => isCountBasedProduct(props.item.product))
|
||||
const quantityUnit = computed(() => getProductQuantityUnit(props.item.product))
|
||||
|
||||
const displayImage = computed(() =>
|
||||
getProductDisplayImage({
|
||||
group_type: props.item.product.group_type,
|
||||
@@ -72,8 +84,49 @@ const displayImage = computed(() =>
|
||||
})
|
||||
)
|
||||
|
||||
const displayGold = computed(() => {
|
||||
const gold = Number(props.item.product.wallet?.gold ?? 0)
|
||||
return isCountBased.value ? usePriceWithoutType(gold, false) : formatGoldAmount(gold)
|
||||
})
|
||||
|
||||
const config = useConfigStore()
|
||||
|
||||
function onCountInput(raw: string | number) {
|
||||
countError.value = ''
|
||||
const normalized = persianToEnglishDigits(String(raw ?? '').trim())
|
||||
if (!normalized) {
|
||||
props.item.count = 0 as any
|
||||
return
|
||||
}
|
||||
|
||||
const numeric = Number(normalized)
|
||||
if (!Number.isFinite(numeric)) {
|
||||
countError.value = 'مقدار معتبر وارد کنید'
|
||||
return
|
||||
}
|
||||
|
||||
if (isCountBased.value) {
|
||||
if (numeric % 1 !== 0) {
|
||||
countError.value = 'برای محصول تعدادی فقط عدد صحیح مجاز است'
|
||||
props.item.count = Math.floor(numeric) as any
|
||||
return
|
||||
}
|
||||
props.item.count = Math.floor(numeric) as any
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.item.count,
|
||||
(value) => {
|
||||
if (!isCountBased.value) return
|
||||
const numeric = Number(value)
|
||||
if (Number.isFinite(numeric) && numeric % 1 !== 0) {
|
||||
props.item.count = Math.floor(numeric) as any
|
||||
countError.value = 'برای محصول تعدادی فقط عدد صحیح مجاز است'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
remove: [id: number]
|
||||
}>()
|
||||
|
||||
@@ -8,13 +8,28 @@ import { physicalReceiveApi } from '@/api/physicalreceive'
|
||||
import { tableAddress } from '@/api/address'
|
||||
import type { CountPInventory, PInventory } from '@/models/products'
|
||||
import type { Address } from '@/models/address'
|
||||
import type { WalletAssetsSummary } from '@/models/walletAssets'
|
||||
import { loading } from '@/utils'
|
||||
import { enrichInventoryWithCatalog } from '@/utils/productAssets'
|
||||
import {
|
||||
enrichInventoryWithCatalog,
|
||||
getProductInventoryQuantity,
|
||||
} from '@/utils/productAssets'
|
||||
import { resolvePhysicalDeliveryState } from '@/utils/physicalDelivery'
|
||||
import { isCountBasedProduct } from '@/utils/productDealMethod'
|
||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||
import { useWalletStore } from '@/store/walletStore'
|
||||
import { usePhysicalReceiveShellContext } from './usePhysicalReceiveShell'
|
||||
|
||||
export interface PendingDeliveryItem {
|
||||
id: number
|
||||
title: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface NewPhysicalRequestContext {
|
||||
isDesktop: Ref<boolean>
|
||||
items: Ref<PInventory[] | undefined>
|
||||
pendingDeliveryItems: Ref<PendingDeliveryItem[]>
|
||||
selectedProduct: Ref<number | undefined>
|
||||
selectedProducts: Ref<CountPInventory[]>
|
||||
address: Ref<Address[] | undefined>
|
||||
@@ -31,11 +46,37 @@ export interface NewPhysicalRequestContext {
|
||||
export const NEW_PHYSICAL_REQUEST_KEY: InjectionKey<NewPhysicalRequestContext> =
|
||||
Symbol('newPhysicalRequest')
|
||||
|
||||
function syncInventoryWithWallet(
|
||||
products: PInventory[],
|
||||
summary: WalletAssetsSummary | null | undefined,
|
||||
mainWalletGold?: number | null
|
||||
): PInventory[] {
|
||||
const inventoryCards = summary?.inventory_cards ?? []
|
||||
|
||||
return products.map((product) => {
|
||||
const gold = getProductInventoryQuantity(inventoryCards, product.id, mainWalletGold)
|
||||
if (!product.wallet) {
|
||||
return product
|
||||
}
|
||||
|
||||
return {
|
||||
...product,
|
||||
wallet: {
|
||||
...product.wallet,
|
||||
gold,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function useNewPhysicalRequestPage() {
|
||||
const { isDesktop } = usePhysicalReceiveShellContext()
|
||||
const { loadWalletAssets, assetsSummary } = useWalletAssets()
|
||||
const walletStore = useWalletStore()
|
||||
|
||||
const items = ref<PInventory[]>()
|
||||
const baseItems = ref<PInventory[]>()
|
||||
const pendingDeliveryItems = ref<PendingDeliveryItem[]>([])
|
||||
const selectedAddressID = ref<number>()
|
||||
const selectedProduct = ref<number>()
|
||||
const productsByWalletId = ref<Array<{ wallet_id: number; value: number }>>([])
|
||||
@@ -76,10 +117,37 @@ export function useNewPhysicalRequestPage() {
|
||||
const [inventory, catalog] = await Promise.all([
|
||||
getProductInventory(),
|
||||
getProductsCatalog(),
|
||||
loadWalletAssets(),
|
||||
])
|
||||
const enriched = enrichInventoryWithCatalog(inventory.rows ?? [], catalog ?? [])
|
||||
items.value = enriched
|
||||
baseItems.value = cloneDeep(enriched)
|
||||
const withBalance = syncInventoryWithWallet(
|
||||
enrichInventoryWithCatalog(inventory.rows ?? [], catalog ?? []),
|
||||
assetsSummary.value,
|
||||
walletStore.walletInfo?.gold
|
||||
).filter(
|
||||
(product) =>
|
||||
product.title !== 'آبشده نقدی' && Number(product.wallet?.gold ?? 0) > 0
|
||||
)
|
||||
|
||||
// فقط محصولاتی که تحویل فیزیکی دارند و به حداقل گرم رسیدهاند قابل انتخاباند.
|
||||
// محصولاتی که تحویل دارند ولی هنوز به حداقل نرسیدهاند بهصورت پیام نمایش داده میشوند.
|
||||
const eligible: PInventory[] = []
|
||||
const pending: PendingDeliveryItem[] = []
|
||||
for (const product of withBalance) {
|
||||
const state = resolvePhysicalDeliveryState(product as any)
|
||||
if (state.status === 'ready') {
|
||||
eligible.push(product)
|
||||
} else if (state.status === 'below_min') {
|
||||
pending.push({
|
||||
id: product.id,
|
||||
title: product.title ?? 'محصول',
|
||||
message: `${product.title ?? 'محصول'}: ${state.message}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
items.value = eligible
|
||||
baseItems.value = cloneDeep(eligible)
|
||||
pendingDeliveryItems.value = pending
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
} finally {
|
||||
@@ -91,10 +159,21 @@ export function useNewPhysicalRequestPage() {
|
||||
try {
|
||||
const zeroAmount = selectedProducts.value.map((item) => item.count <= 0)
|
||||
if (zeroAmount.includes(true)) {
|
||||
message.error('لطفاً تعداد درخواستی محصول را وارد کنید.')
|
||||
} else {
|
||||
showAddress.value = true
|
||||
message.error('لطفاً مقدار درخواستی محصول را وارد کنید.')
|
||||
return
|
||||
}
|
||||
|
||||
const invalidCount = selectedProducts.value.find((item) => {
|
||||
if (!isCountBasedProduct(item.product)) return false
|
||||
const value = Number(item.count)
|
||||
return !Number.isFinite(value) || value % 1 !== 0
|
||||
})
|
||||
if (invalidCount) {
|
||||
message.error(`برای «${invalidCount.product.title}» فقط تعداد صحیح مجاز است`)
|
||||
return
|
||||
}
|
||||
|
||||
showAddress.value = true
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
@@ -145,6 +224,7 @@ export function useNewPhysicalRequestPage() {
|
||||
const context: NewPhysicalRequestContext = {
|
||||
isDesktop,
|
||||
items,
|
||||
pendingDeliveryItems,
|
||||
selectedProduct,
|
||||
selectedProducts,
|
||||
address,
|
||||
|
||||
@@ -22,6 +22,17 @@
|
||||
<p v-if="items?.length" class="dpr-new__hint">
|
||||
{{ items.length }} محصول با موجودی قابل تحویل
|
||||
</p>
|
||||
|
||||
<div v-if="pendingDeliveryItems.length" class="dpr-new__pending">
|
||||
<p class="dpr-new__pending-title">نزدیک به حد تحویل فیزیکی</p>
|
||||
<p
|
||||
v-for="pending in pendingDeliveryItems"
|
||||
:key="pending.id"
|
||||
class="dpr-new__pending-row"
|
||||
>
|
||||
{{ pending.message }}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="dpr-surface dpr-new__cart">
|
||||
@@ -104,6 +115,7 @@ import { useNewPhysicalRequestContext } from '../composables/useNewPhysicalReque
|
||||
|
||||
const {
|
||||
items,
|
||||
pendingDeliveryItems,
|
||||
selectedProduct,
|
||||
selectedProducts,
|
||||
address,
|
||||
@@ -117,3 +129,26 @@ const {
|
||||
handleAddress,
|
||||
} = useNewPhysicalRequestContext()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dpr-new__pending {
|
||||
margin-top: 0.75rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
background: #fff7ed;
|
||||
border: 1px solid #fed7aa;
|
||||
}
|
||||
|
||||
.dpr-new__pending-title {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: #c2410c;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.dpr-new__pending-row {
|
||||
font-size: 0.78rem;
|
||||
color: #9a3412;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,6 +17,17 @@
|
||||
افزودن
|
||||
</BasicButton>
|
||||
</div>
|
||||
|
||||
<div v-if="pendingDeliveryItems.length" class="physical-new-request__pending mx-3 mt-2">
|
||||
<p class="physical-new-request__pending-title">نزدیک به حد تحویل فیزیکی</p>
|
||||
<p
|
||||
v-for="pending in pendingDeliveryItems"
|
||||
:key="pending.id"
|
||||
class="physical-new-request__pending-row"
|
||||
>
|
||||
{{ pending.message }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="physical-new-request__items px-3">
|
||||
@@ -67,6 +78,7 @@ import { useNewPhysicalRequestContext } from '../composables/useNewPhysicalReque
|
||||
|
||||
const {
|
||||
items,
|
||||
pendingDeliveryItems,
|
||||
selectedProduct,
|
||||
selectedProducts,
|
||||
address,
|
||||
@@ -122,4 +134,24 @@ const {
|
||||
background: #fff;
|
||||
border-top: 1px solid #f3f4f6;
|
||||
}
|
||||
|
||||
.physical-new-request__pending {
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
background: #fff7ed;
|
||||
border: 1px solid #fed7aa;
|
||||
}
|
||||
|
||||
.physical-new-request__pending-title {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: #c2410c;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.physical-new-request__pending-row {
|
||||
font-size: 0.78rem;
|
||||
color: #9a3412;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -118,6 +118,15 @@
|
||||
<div>{{ productWalletBalanceText }}</div>
|
||||
</div>
|
||||
</Transition>
|
||||
<Transition mode="out-in" name="fade">
|
||||
<div
|
||||
v-if="physicalDeliveryHint"
|
||||
class="text-xs mt-1"
|
||||
:class="physicalDeliveryState.status === 'ready' ? 'text-green-600' : 'text-orange-600'"
|
||||
>
|
||||
{{ physicalDeliveryHint }}
|
||||
</div>
|
||||
</Transition>
|
||||
<Transition mode="out-in" name="fade">
|
||||
<div v-if="priceErrorVisible" class="flex gap-1 items-center text-xs mt-[-15px] text-orange-600">
|
||||
<BasicIcon icon="vuesax-linear:danger" />
|
||||
@@ -321,6 +330,7 @@ import {
|
||||
} from '@/utils/productOrderLimit'
|
||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||
import { formatProductWalletBalanceText, formatInventoryCardValue, getCashWalletBalance, getProductInventoryQuantity } from '@/utils/productAssets'
|
||||
import { resolvePhysicalDeliveryState } from '@/utils/physicalDelivery'
|
||||
import { isStoreClosed, resolveStoreClosedNotice } from '@/utils/storeHours'
|
||||
|
||||
const catalogProducts = getProductsCatalogRef()
|
||||
@@ -371,6 +381,24 @@ const productWalletBalanceText = computed(() =>
|
||||
)
|
||||
)
|
||||
|
||||
const physicalDeliveryState = computed(() =>
|
||||
resolvePhysicalDeliveryState(props?.ProductItem as any, productWalletBalance.value)
|
||||
)
|
||||
|
||||
const physicalDeliveryHint = computed(() => {
|
||||
if (!props?.ProductItem?.has_physical_delivery) {
|
||||
return ''
|
||||
}
|
||||
const state = physicalDeliveryState.value
|
||||
if (state.status === 'ready') {
|
||||
return 'امکان تحویل فیزیکی این محصول فعال است'
|
||||
}
|
||||
if (state.status === 'below_min') {
|
||||
return state.message
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const infoVisible = ref(false)
|
||||
const walletStore = useWalletStore()
|
||||
const { assetsSummary, loadWalletAssets } = useWalletAssets()
|
||||
|
||||
Reference in New Issue
Block a user