update
This commit is contained in:
@@ -26,7 +26,7 @@ const _attrs = computed(() => {
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
value: '',
|
||||
format: 'YYYY/MM/DD'
|
||||
format: 'YYYY-MM-DD'
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -17,16 +17,25 @@ export interface Product {
|
||||
percent_fee_sell: number | undefined
|
||||
cash_fee_buy: number | undefined
|
||||
cash_fee_sell: number | undefined
|
||||
cash_fee_buy_is_percent?: boolean | null
|
||||
cash_fee_sell_is_percent?: boolean | null
|
||||
reading_method: any
|
||||
created_at: any
|
||||
updated_at: any
|
||||
deleted_at: any
|
||||
price?:number | undefined
|
||||
price_profit?: number | null
|
||||
price_offset_percent?: number | null
|
||||
sellPrice?:number | undefined
|
||||
description: string
|
||||
wallet: Wallet
|
||||
similar?:Product[]
|
||||
inventory:number
|
||||
app_price?: boolean | number | null
|
||||
direct_price?: number | null
|
||||
trade_by_quantity?: boolean | number | null
|
||||
use_last_price_if_zero?: boolean | null
|
||||
reference_provider_active?: boolean | null
|
||||
}
|
||||
|
||||
export interface PInventory {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
export type ProductCashFeeSource = {
|
||||
cash_fee_buy?: number | null
|
||||
cash_fee_sell?: number | null
|
||||
cash_fee_buy_is_percent?: boolean | null
|
||||
cash_fee_sell_is_percent?: boolean | null
|
||||
}
|
||||
|
||||
/** 0.5 => 0.5% ، 5 => 5% */
|
||||
export function resolveCashFeePercentRatio(percentValue?: number | null) {
|
||||
const offset = Number(percentValue ?? 0)
|
||||
if (!offset || !Number.isFinite(offset)) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return offset / 100
|
||||
}
|
||||
|
||||
export function resolveProductCashFeeRial(
|
||||
feeValue: number | null | undefined,
|
||||
isPercent: boolean | null | undefined,
|
||||
basePriceRial: number,
|
||||
) {
|
||||
const raw = Number(feeValue ?? 0)
|
||||
if (!raw || !Number.isFinite(raw)) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if (!isPercent) {
|
||||
return Math.round(raw)
|
||||
}
|
||||
|
||||
const base = Number(basePriceRial)
|
||||
if (!base || base <= 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return Math.round(Math.abs(base * resolveCashFeePercentRatio(raw)))
|
||||
}
|
||||
|
||||
export function resolveProductCashFeeBuyRial(product: ProductCashFeeSource, basePriceRial: number) {
|
||||
return resolveProductCashFeeRial(
|
||||
product.cash_fee_buy,
|
||||
product.cash_fee_buy_is_percent,
|
||||
basePriceRial,
|
||||
)
|
||||
}
|
||||
|
||||
export function resolveProductCashFeeSellRial(product: ProductCashFeeSource, basePriceRial: number) {
|
||||
return resolveProductCashFeeRial(
|
||||
product.cash_fee_sell,
|
||||
product.cash_fee_sell_is_percent,
|
||||
basePriceRial,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/** دستهبندی یکسان محصول — همتراز با khaneghi_user */
|
||||
export const PRODUCT_CATEGORY_ABSHODE = 'آبشده'
|
||||
|
||||
export const LEGACY_GROUP_KATOD = 'کاتد مس'
|
||||
|
||||
export const LEGACY_MELTED_GROUP_TYPES = [LEGACY_GROUP_KATOD] as const
|
||||
|
||||
export function normalizeProductGroupType(groupType?: string | null) {
|
||||
return (groupType ?? '').trim()
|
||||
}
|
||||
|
||||
/** محصول آبشدهای — قیمت از مظنه اپ (مگر قیمت ثابت / مرجع) */
|
||||
export function isMeltedTradeGroup(groupType?: string | null) {
|
||||
const group = normalizeProductGroupType(groupType)
|
||||
return group === PRODUCT_CATEGORY_ABSHODE
|
||||
|| LEGACY_MELTED_GROUP_TYPES.includes(group as typeof LEGACY_MELTED_GROUP_TYPES[number])
|
||||
}
|
||||
|
||||
export interface QuantityTradeProductSource {
|
||||
group_type?: string | null
|
||||
trade_by_quantity?: boolean | number | null
|
||||
}
|
||||
|
||||
export function usesQuantityTrade(product?: QuantityTradeProductSource | null) {
|
||||
if (!product) {
|
||||
return false
|
||||
}
|
||||
|
||||
return product.trade_by_quantity === true || Number(product.trade_by_quantity) === 1
|
||||
}
|
||||
|
||||
export function isMetalCommodityGroup(groupType?: string | null) {
|
||||
const group = normalizeProductGroupType(groupType)
|
||||
return group === 'مس' || group === 'نقره'
|
||||
}
|
||||
|
||||
export type MeltedPriceSource = 'app' | 'fixed' | 'reference'
|
||||
|
||||
export function resolveMeltedPriceSource(product?: {
|
||||
reading_method?: string | null
|
||||
direct_price?: number | null
|
||||
} | null): MeltedPriceSource {
|
||||
if (product?.reading_method) {
|
||||
return 'reference'
|
||||
}
|
||||
if (Number(product?.direct_price) === 1) {
|
||||
return 'fixed'
|
||||
}
|
||||
return 'app'
|
||||
}
|
||||
|
||||
/** محصول با پرچم صریح «قیمت اپ» */
|
||||
export function isAppPriceProduct(product?: {
|
||||
app_price?: boolean | number | null
|
||||
reading_method?: string | null
|
||||
} | null) {
|
||||
if (!product || product.reading_method) {
|
||||
return false
|
||||
}
|
||||
return product.app_price === true || Number(product.app_price) === 1
|
||||
}
|
||||
|
||||
export function isMeltedProductUsingAppPrice(product?: {
|
||||
group_type?: string | null
|
||||
reading_method?: string | null
|
||||
direct_price?: number | null
|
||||
trade_by_quantity?: boolean | number | null
|
||||
price?: number | null
|
||||
} | null) {
|
||||
if (!product || resolveMeltedPriceSource(product) !== 'app') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (isMeltedTradeGroup(product.group_type)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (!usesQuantityTrade(product)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (Number(product.direct_price) === 1) {
|
||||
return false
|
||||
}
|
||||
|
||||
return Number(product.price ?? 0) <= 0
|
||||
}
|
||||
+201
-36
@@ -1,93 +1,257 @@
|
||||
import type { Product } from '@/models/products'
|
||||
import { getTradeBuyPrice, getTradeSellPrice, lastPrice } from '@/utils/price'
|
||||
import { resolveProductCashFeeBuyRial, resolveProductCashFeeSellRial } from '@/utils/productCashFee'
|
||||
import {
|
||||
isAppPriceProduct,
|
||||
isMeltedProductUsingAppPrice,
|
||||
isMeltedTradeGroup,
|
||||
isMetalCommodityGroup,
|
||||
usesQuantityTrade,
|
||||
} from '@/utils/productCategories'
|
||||
|
||||
/**
|
||||
* قیمتگیری ماژولار محصول در حالت فروشگاهی (is_store=true).
|
||||
*
|
||||
* منبع حقیقت قیمت و وضعیت نمایش، سرور است: `/customer/product/list` و رویداد SSE
|
||||
* `list` هر دو قیمت هر محصول (`price`/`price_profit`) و `show` را محاسبهشده میفرستند.
|
||||
* این ماژول فقط همان قیمت سرور را میخواند و فقط برای آبشده وقتی قیمت سرور صفر است
|
||||
* به قیمت زندهٔ مرجع (`lastPrice`) fallback میکند — هیچجا قیمت سرور بازنویسی نمیشود.
|
||||
* قیمتگیری محصول — همتراز با khaneghi_user:
|
||||
* آبشده / قیمتاپ از مظنه زنده؛ قیمت ثابت و مرجع از سرور.
|
||||
*/
|
||||
|
||||
const MELTED_GROUP = 'آبشده'
|
||||
|
||||
export function isMeltedProduct(product?: Product | null): boolean {
|
||||
return product?.group_type === MELTED_GROUP
|
||||
function productUsesLastPriceIfZero(product: Product): boolean {
|
||||
return Boolean(product.use_last_price_if_zero)
|
||||
}
|
||||
|
||||
/** قیمت پایهٔ خرید (بدون کارمزد) — قیمت سرور، با fallback زندهٔ آبشده وقتی سرور صفر است */
|
||||
function hasReferenceReadingMethod(product: Product): boolean {
|
||||
return Boolean(product.reading_method)
|
||||
}
|
||||
|
||||
function hasProductUnitWeight(product?: Product | null) {
|
||||
if (!product || isMeltedTradeGroup(product.group_type)) {
|
||||
return false
|
||||
}
|
||||
return Number(product.amount ?? 0) > 0
|
||||
}
|
||||
|
||||
function productPriceAmountFactor(product?: Product | null) {
|
||||
if (!product || product.reading_method) {
|
||||
return 1
|
||||
}
|
||||
if (isAppPriceProduct(product)) {
|
||||
const amount = Number(product.amount ?? 0)
|
||||
return amount > 0 ? amount : 1
|
||||
}
|
||||
if (hasProductUnitWeight(product) && Number(product.direct_price ?? 0) === 0) {
|
||||
const amount = Number(product.amount ?? 0)
|
||||
return amount > 0 ? amount : 1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
function getReferenceProductBaseBuyPrice(product: Product): number {
|
||||
const storedUnit = Number(product.price ?? 0)
|
||||
const useLast = productUsesLastPriceIfZero(product)
|
||||
|
||||
if (!useLast) {
|
||||
return storedUnit > 0 ? storedUnit : 0
|
||||
}
|
||||
|
||||
return storedUnit
|
||||
}
|
||||
|
||||
export function resolvePriceOffsetRatio(offsetPercent?: number | null) {
|
||||
const offset = Number(offsetPercent ?? 0)
|
||||
if (!offset) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if (Math.abs(offset) > 0 && Math.abs(offset) < 1) {
|
||||
return offset
|
||||
}
|
||||
|
||||
return offset / 100
|
||||
}
|
||||
|
||||
export function applyPriceOffsetPercent(basePrice: number, offsetPercent?: number | null) {
|
||||
const ratio = resolvePriceOffsetRatio(offsetPercent)
|
||||
if (!ratio) {
|
||||
return basePrice
|
||||
}
|
||||
|
||||
return Math.round(basePrice * (1 + ratio))
|
||||
}
|
||||
|
||||
/** قیمت پایه بدون کارمزد */
|
||||
export function getProductBaseBuyPrice(product?: Product | null): number {
|
||||
if (!product) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const serverPrice = Number(product.price ?? 0)
|
||||
if (serverPrice > 0) {
|
||||
return serverPrice
|
||||
}
|
||||
if (isMeltedProductUsingAppPrice(product) || isAppPriceProduct(product)) {
|
||||
const apiPrice = Number(product.price ?? 0)
|
||||
const useLast = productUsesLastPriceIfZero(product)
|
||||
|
||||
if (!useLast) {
|
||||
const liveBuy = getTradeBuyPrice(lastPrice.value)
|
||||
if (liveBuy > 0) {
|
||||
return applyPriceOffsetPercent(liveBuy, product.price_offset_percent)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
if (apiPrice > 0) {
|
||||
return apiPrice
|
||||
}
|
||||
|
||||
if (isMeltedProduct(product)) {
|
||||
const liveBuy = getTradeBuyPrice(lastPrice.value)
|
||||
if (liveBuy > 0) {
|
||||
const amount = Number(product.amount ?? 0)
|
||||
return amount > 0 ? liveBuy * amount : liveBuy
|
||||
return applyPriceOffsetPercent(liveBuy, product.price_offset_percent)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
return 0
|
||||
if (isMeltedTradeGroup(product.group_type)) {
|
||||
return Number(product.price ?? 0)
|
||||
}
|
||||
|
||||
if (isMetalCommodityGroup(product.group_type)) {
|
||||
return Number(product.price_profit ?? 0) + Number(product.price ?? 0)
|
||||
}
|
||||
|
||||
if (hasReferenceReadingMethod(product)) {
|
||||
return getReferenceProductBaseBuyPrice(product)
|
||||
}
|
||||
|
||||
return Number(product.price ?? 0)
|
||||
}
|
||||
|
||||
/** قیمت خرید نمایشی محصول (همان مقداری که در لیست و صفحهٔ محصول نشان داده میشود) */
|
||||
/** قیمت خرید نمایشی */
|
||||
export function getProductBuyPrice(product?: Product | null): number {
|
||||
if (!product) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const base = getProductBaseBuyPrice(product)
|
||||
if (base <= 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return base + Number(product?.cash_fee_buy ?? 0)
|
||||
const fee = resolveProductCashFeeBuyRial(product, base)
|
||||
const withFee = base + fee
|
||||
|
||||
if (isAppPriceProduct(product)) {
|
||||
return withFee * productPriceAmountFactor(product)
|
||||
}
|
||||
|
||||
if (
|
||||
isMeltedProductUsingAppPrice(product)
|
||||
|| isMeltedTradeGroup(product.group_type)
|
||||
|| isMetalCommodityGroup(product.group_type)
|
||||
) {
|
||||
return withFee
|
||||
}
|
||||
|
||||
if (usesQuantityTrade(product) && hasProductUnitWeight(product)) {
|
||||
return withFee * productPriceAmountFactor(product)
|
||||
}
|
||||
|
||||
if (usesQuantityTrade(product)) {
|
||||
return withFee
|
||||
}
|
||||
|
||||
return withFee * productPriceAmountFactor(product)
|
||||
}
|
||||
|
||||
/** قیمت پایهٔ فروش (بدون کارمزد) — قیمت فروش سرور، با fallback زندهٔ آبشده */
|
||||
/** قیمت پایه فروش */
|
||||
export function getProductBaseSellPrice(product?: Product | null): number {
|
||||
if (!product) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if (isMeltedProductUsingAppPrice(product) || isAppPriceProduct(product)) {
|
||||
const apiPrice = Number(product.price ?? 0)
|
||||
const useLast = productUsesLastPriceIfZero(product)
|
||||
|
||||
if (!useLast) {
|
||||
const liveSell = getTradeSellPrice(lastPrice.value)
|
||||
if (liveSell > 0) {
|
||||
return applyPriceOffsetPercent(liveSell, product.price_offset_percent)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
if (apiPrice > 0) {
|
||||
return apiPrice
|
||||
}
|
||||
|
||||
const liveSell = getTradeSellPrice(lastPrice.value)
|
||||
if (liveSell > 0) {
|
||||
return applyPriceOffsetPercent(liveSell, product.price_offset_percent)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
if (isMeltedTradeGroup(product.group_type)) {
|
||||
return Number(product.sellPrice ?? product.price ?? 0)
|
||||
}
|
||||
|
||||
const serverSell = Number(product.sellPrice ?? 0)
|
||||
if (serverSell > 0) {
|
||||
return serverSell
|
||||
}
|
||||
|
||||
if (isMeltedProduct(product)) {
|
||||
const liveSell = getTradeSellPrice(lastPrice.value)
|
||||
if (liveSell > 0) {
|
||||
const amount = Number(product.amount ?? 0)
|
||||
return amount > 0 ? liveSell * amount : liveSell
|
||||
}
|
||||
}
|
||||
|
||||
return getProductBaseBuyPrice(product)
|
||||
}
|
||||
|
||||
/** قیمت فروش نمایشی محصول */
|
||||
/** قیمت فروش نمایشی */
|
||||
export function getProductSellPrice(product?: Product | null): number {
|
||||
if (!product) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const base = getProductBaseSellPrice(product)
|
||||
if (base <= 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return base - Number(product?.cash_fee_sell ?? 0)
|
||||
if (isAppPriceProduct(product)) {
|
||||
const withFee = base - resolveProductCashFeeSellRial(product, base)
|
||||
return withFee * productPriceAmountFactor(product)
|
||||
}
|
||||
|
||||
if (isMeltedProductUsingAppPrice(product) || isMeltedTradeGroup(product.group_type)) {
|
||||
return base - resolveProductCashFeeSellRial(product, base)
|
||||
}
|
||||
|
||||
if (usesQuantityTrade(product) && hasProductUnitWeight(product)) {
|
||||
const withFee = base - resolveProductCashFeeSellRial(product, base)
|
||||
return withFee * productPriceAmountFactor(product)
|
||||
}
|
||||
|
||||
if (usesQuantityTrade(product)) {
|
||||
return base - resolveProductCashFeeSellRial(product, base)
|
||||
}
|
||||
|
||||
return base - resolveProductCashFeeSellRial(product, base)
|
||||
}
|
||||
|
||||
/** محصول وقتی قیمت پایهٔ معتبری ندارد، غیرفعال (غیرقابل معامله) است */
|
||||
export function isProductPriceInactive(product?: Product | null): boolean {
|
||||
if (!product) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (
|
||||
hasReferenceReadingMethod(product)
|
||||
&& !productUsesLastPriceIfZero(product)
|
||||
&& product.reference_provider_active === false
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
return getProductBaseBuyPrice(product) <= 0
|
||||
}
|
||||
|
||||
/**
|
||||
* نمایش محصول در فروشگاه — اولویت با `show` سرور.
|
||||
* فقط برای آبشدهای که قیمت سرورش هنوز نرسیده، با قیمت زنده تکمیل میکنیم.
|
||||
* آبشدهٔ قیمتاپ با مظنه زنده حتی اگر show هنوز false باشد.
|
||||
*/
|
||||
export function isProductVisible(product?: Product | null): boolean {
|
||||
if (!product || product.active === false) {
|
||||
@@ -98,6 +262,7 @@ export function isProductVisible(product?: Product | null): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
// سازگاری: آبشدهای که سرور هنوز قیمت نداده ولی قیمت زنده موجود است
|
||||
return isMeltedProduct(product) && getProductBaseBuyPrice(product) > 0
|
||||
return isMeltedProductUsingAppPrice(product) && getProductBaseBuyPrice(product) > 0
|
||||
}
|
||||
|
||||
export { isMeltedTradeGroup, isMeltedProductUsingAppPrice } from '@/utils/productCategories'
|
||||
|
||||
Reference in New Issue
Block a user