fix bugs of physicalRecieve & login-code-input & odometer-digit
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import type { PInventory, Product } from '@/models/products'
|
||||
import { getProductQuantityUnit, isCountBasedProduct } from '@/utils/productDealMethod'
|
||||
import { formatWeightAmount, usePriceWithoutType } from '@/utils/usePrice'
|
||||
|
||||
export type PhysicalDeliveryStatus = 'none' | 'ready' | 'below_min'
|
||||
|
||||
/** برچسب کوتاه کارت محصول: فقط «دارد/ندارد» تحویل فیزیکی — مستقل از موجودی کاربر */
|
||||
export function getProductDeliveryBadge(
|
||||
product: { has_physical_delivery?: boolean } | null | undefined
|
||||
): { has: boolean; text: string } {
|
||||
const has = Boolean(product?.has_physical_delivery)
|
||||
return {
|
||||
has,
|
||||
text: has ? 'تحویل فیزیکی دارد' : 'تحویل فیزیکی ندارد',
|
||||
}
|
||||
}
|
||||
|
||||
export interface PhysicalDeliveryState {
|
||||
status: PhysicalDeliveryStatus
|
||||
available: number
|
||||
min: number
|
||||
remaining: number
|
||||
unit: string
|
||||
message: string
|
||||
}
|
||||
|
||||
type DeliveryProduct = (Partial<Product> | Partial<PInventory>) & {
|
||||
has_physical_delivery?: boolean
|
||||
min_physical_delivery_quantity?: number | null
|
||||
amount?: any
|
||||
unit?: string | null
|
||||
group_type?: string | null
|
||||
direct_price?: number | null
|
||||
title?: string | null
|
||||
wallet?: { gold?: number | null } | null
|
||||
}
|
||||
|
||||
/**
|
||||
* موجودی محصول به واحد خود محصول:
|
||||
* - تعدادی → تعداد قلم (عدد)
|
||||
* - وزنی → وزن به واحد محصول (گرم/کیلوگرم)
|
||||
* همسان با inventory_cards سرور (wallet.gold مستقیماً به واحد محصول است).
|
||||
*/
|
||||
export function resolveProductQuantity(
|
||||
product: DeliveryProduct | null | undefined,
|
||||
quantity?: number | null
|
||||
): number {
|
||||
if (!product) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const qty = quantity != null ? Number(quantity) : Number(product.wallet?.gold ?? 0)
|
||||
if (!Number.isFinite(qty) || qty <= 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return qty
|
||||
}
|
||||
|
||||
/** قالببندی مقدار بر اساس واحد: عدد بدون اعشار، وزن با فرمت وزنی */
|
||||
function formatDeliveryQuantity(value: number, product: DeliveryProduct | null | undefined): string {
|
||||
if (isCountBasedProduct(product as any)) {
|
||||
return usePriceWithoutType(value, false)
|
||||
}
|
||||
return formatWeightAmount(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* وضعیت تحویل فیزیکی یک محصول:
|
||||
* - none: محصول اصلاً تحویل فیزیکی ندارد
|
||||
* - ready: موجودی به حداقل رسیده و تحویل فعال است
|
||||
* - below_min: تحویل دارد ولی هنوز به حداقل نرسیده — پیام «چند واحد مانده»
|
||||
*/
|
||||
export function resolvePhysicalDeliveryState(
|
||||
product: DeliveryProduct | null | undefined,
|
||||
quantity?: number | null
|
||||
): PhysicalDeliveryState {
|
||||
const title = product?.title?.trim() || 'این محصول'
|
||||
const unit = getProductQuantityUnit(product as any)
|
||||
|
||||
if (!product?.has_physical_delivery) {
|
||||
return {
|
||||
status: 'none',
|
||||
available: 0,
|
||||
min: 0,
|
||||
remaining: 0,
|
||||
unit,
|
||||
message: `${title} تحویل فیزیکی ندارد`,
|
||||
}
|
||||
}
|
||||
|
||||
const min = Math.max(0, Number(product.min_physical_delivery_quantity ?? 0))
|
||||
const available = resolveProductQuantity(product, quantity)
|
||||
|
||||
if (min <= 0 || available + 0.0001 >= min) {
|
||||
return {
|
||||
status: 'ready',
|
||||
available,
|
||||
min,
|
||||
remaining: 0,
|
||||
unit,
|
||||
message: 'امکان تحویل فیزیکی فعال است',
|
||||
}
|
||||
}
|
||||
|
||||
const remaining = Math.max(0, min - available)
|
||||
return {
|
||||
status: 'below_min',
|
||||
available,
|
||||
min,
|
||||
remaining,
|
||||
unit,
|
||||
message: `${formatDeliveryQuantity(remaining, product)} ${unit} مانده تا رسیدن به حداقل تحویل فیزیکی`,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { persianToEnglishDigits } from '@/utils/persianDigits'
|
||||
|
||||
/** استخراج کد عددی از نتیجه WebOTP / Credential */
|
||||
export function extractOtpCode(
|
||||
credential: Credential | null | undefined,
|
||||
length = 4
|
||||
): string {
|
||||
const raw = (credential as { code?: string } | null | undefined)?.code ?? ''
|
||||
return persianToEnglishDigits(raw).replace(/\D/g, '').slice(0, length)
|
||||
}
|
||||
|
||||
/**
|
||||
* گوش دادن به SMS از طریق WebOTP API (Chrome Android).
|
||||
* با کلیک Allow روی دیالوگ مرورگر، onCode با کد ۴ رقمی صدا زده میشود.
|
||||
*/
|
||||
export function listenForSmsOtp(onCode: (code: string) => void): AbortController {
|
||||
const ac = new AbortController()
|
||||
|
||||
if (!('OTPCredential' in window) || !navigator.credentials?.get) {
|
||||
return ac
|
||||
}
|
||||
|
||||
void navigator.credentials
|
||||
.get({
|
||||
otp: { transport: ['sms'] },
|
||||
signal: ac.signal,
|
||||
} as CredentialRequestOptions)
|
||||
.then((credential) => {
|
||||
const code = extractOtpCode(credential, 4)
|
||||
if (code.length === 4) {
|
||||
onCode(code)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
/* aborted or denied — ignore */
|
||||
})
|
||||
|
||||
return ac
|
||||
}
|
||||
Reference in New Issue
Block a user