import type { SelectItem } from '@/models/SelectItem' import type { Product } from '@/models/products' import type { WalletInventoryCard } from '@/models/walletAssets' import { isBoursecuProject } from '@/utils/config' import { useSettingsStore } from '@/store/settingsStore' import { isLegacyMaftolGroup, isMeltedTradeGroup, LEGACY_GROUP_KATOD, LEGACY_GROUP_MAFTOL, normalizeProductGroupType, PRODUCT_CATEGORY_ABSHODE, usesQuantityTrade, } from '@/utils/productCategories' import { formatInventoryCardValue } from '@/utils/productAssets' /** @deprecated از isMeltedTradeGroup استفاده کنید */ export const BOURSECU_GROUP_ABSHODE = PRODUCT_CATEGORY_ABSHODE export const BOURSECU_GROUP_KATOD = LEGACY_GROUP_KATOD export const BOURSECU_GROUP_MAFTOL = LEGACY_GROUP_MAFTOL export const BOURSECU_WALLET_LABEL_GENERIC = 'موجودی کیف' export function isBoursecuDealProductGroup(groupType?: string | null) { if (!isBoursecuProject()) { return false } const group = normalizeProductGroupType(groupType) return group === BOURSECU_GROUP_MAFTOL || group === BOURSECU_GROUP_KATOD || group === BOURSECU_GROUP_ABSHODE || group === 'مس' } export function isMaftolMesGroup(groupType?: string | null) { const group = normalizeProductGroupType(groupType) return group === BOURSECU_GROUP_MAFTOL || (isBoursecuProject() && group === 'مس') } export function isKatodMesGroup(groupType?: string | null) { return normalizeProductGroupType(groupType) === BOURSECU_GROUP_KATOD } export function isShamshMesGroup(groupType?: string | null, title?: string | null) { if (!isBoursecuProject()) { return false } const group = normalizeProductGroupType(groupType) if (group === 'شمش') { return true } return (title ?? '').includes('شمش') } export function isGoldOnlyDealGroup(groupType?: string | null) { return isMeltedTradeGroup(groupType) } export interface ProductUnitSource { unit?: string | null direct_price?: number | null group_type?: string | null amount?: number | null trade_by_quantity?: boolean | number | null } export function isWeightBasedProductUnit(unit?: string | null) { const value = (unit ?? '').trim() return value === 'کیلوگرم' || value === 'گرم' } const COUNT_BASED_GROUP_TYPES = ['سکه', 'گوی', 'مکعب', 'شمش', 'سکه پارسیان'] /** مطابق ادمین: وزن «دارد» => amount > 0 ؛ «ندارد» => amount = 0 */ export function hasProductUnitWeight(product?: ProductUnitSource | null) { if (isMeltedTradeGroup(product?.group_type)) { return false } const amount = Number(product?.amount ?? 0) return amount > 0 } /** ضریب قیمت — فقط «متغیر» بدون مرجع اتوماتیک: قیمت هر گرم × وزن */ export function productPriceAmountFactor(product?: ProductUnitSource | null) { if (product?.reading_method) { return 1 } if (isCountBasedProduct(product)) { return 1 } if (Number(product?.direct_price ?? 0) !== 0) { return 1 } if (hasProductUnitWeight(product)) { const amount = Number(product?.amount ?? 0) return amount > 0 ? amount : 1 } return 1 } export function getProductWeightUnit(product?: ProductUnitSource | null) { const unit = product?.unit?.trim() if (unit && isWeightBasedProductUnit(unit)) { return unit } if (isBoursecuProject()) { const group = normalizeProductGroupType(product?.group_type) if (group === BOURSECU_GROUP_MAFTOL || group === BOURSECU_GROUP_KATOD || group === BOURSECU_GROUP_ABSHODE || group === 'مس' || isMeltedTradeGroup(group)) { return getWeightUnit() } } if (!isCountBasedProduct(product) || isMeltedTradeGroup(product?.group_type)) { return getWeightUnit() } return 'گرم' } export function formatProductWeightAmount(amount?: number | null, product?: ProductUnitSource | null) { const value = Number(amount ?? 0) const weightUnit = getProductWeightUnit(product) if (weightUnit === 'گرم' && value > 0 && value < 1) { return `${value * 1000} سوت` } return `${value} ${weightUnit}` } /** فقط محصول تعدادی (مثل سکه): «هر عدد X گرم» — معامله مقداری با trade_by_quantity نیست */ export function shouldShowPerUnitWeightHint(product?: ProductUnitSource | null) { if (!product || !hasProductUnitWeight(product) || usesQuantityTrade(product)) { return false } return isCountBasedProduct(product) } export function getProductUnitWeightLabel(product?: ProductUnitSource | null) { if (!shouldShowPerUnitWeightHint(product)) { return null } return formatProductWeightAmount(product?.amount, product) } export function getProductTotalWeightLabel(count?: number | null, product?: ProductUnitSource | null) { if (!hasProductUnitWeight(product)) { return null } const quantity = Number(count ?? 0) return formatProductWeightAmount(quantity * Number(product?.amount ?? 0), product) } export function shouldShowProductTotalWeight(product?: ProductUnitSource | null) { if (!product) { return false } if (!isCountBasedProduct(product)) { return true } return hasProductUnitWeight(product) } export function formatProductInventoryBalance(product?: (ProductUnitSource & { wallet?: { gold?: number | null } }) | null) { const count = product?.wallet?.gold ?? 0 const quantityPart = `${count} ${getProductQuantityUnit(product)}` const unitWeight = getProductUnitWeightLabel(product) if (!unitWeight) { return quantityPart } return `${quantityPart} · هر عدد ${unitWeight}` } /** محصول تعدادی: موجودی wallet.gold = تعداد قلم، نه وزن — معامله مقداری با trade_by_quantity نیست */ export function isCountBasedProduct(product?: ProductUnitSource | null) { if (!product) { return false } if (usesQuantityTrade(product)) { return false } if (isWeightBasedProductUnit(product.unit)) { return false } if (product.direct_price != null && product.direct_price !== 0) { return true } const group = normalizeProductGroupType(product.group_type) if (group === BOURSECU_GROUP_MAFTOL || isLegacyMaftolGroup(group) || (isBoursecuProject() && group === 'مس')) { return true } return COUNT_BASED_GROUP_TYPES.includes(group) } export function getProductQuantityUnit(product?: ProductUnitSource | null) { const unit = product?.unit?.trim() if (unit) { return unit } if (isCountBasedProduct(product)) { return 'عدد' } return getWeightUnit() } export const MELTED_DEAL_METHOD_PREFIX = 'gold:' export interface ProductDealMethodContext { tradedProduct?: Product | null productMetalId?: number | null inventoryCards?: WalletInventoryCard[] catalog?: Product[] } /** سلکت نحوه معامله فقط وقتی محصول مورد معامله وزن دارد */ export function shouldShowProductDealMethodSelector(product?: Product | null) { return hasProductUnitWeight(product) } export function shouldShowProductDealMethodSelect( product?: Product | null, options?: SelectItem[] | null, ) { return shouldShowProductDealMethodSelector(product) && (options?.length ?? 0) > 1 } export function buildMeltedDealMethodKey(productId: number) { return `${MELTED_DEAL_METHOD_PREFIX}${productId}` } export function parseMeltedDealMethodProductId(method?: string | null): number | null { if (!method?.startsWith(MELTED_DEAL_METHOD_PREFIX)) { return null } const productId = Number(method.slice(MELTED_DEAL_METHOD_PREFIX.length)) return productId > 0 ? productId : null } export function isMeltedProductDealMethod(method?: string | null) { return parseMeltedDealMethodProductId(method) != null } function isQuantityTradeCatalogProduct( product?: Product | null, metalId?: number | null, excludeProductId?: number | null, ) { if (!product || !usesQuantityTrade(product)) { return false } if (excludeProductId && Number(product.id) === Number(excludeProductId)) { return false } return Number(product.metal_id) === Number(metalId ?? 0) } function findInventoryCardForProduct( cards: WalletInventoryCard[], productId?: number | null, ) { if (!productId) { return undefined } return cards.find((card) => Number(card.product_id) === Number(productId)) } export function getQuantityTradePaymentDealOptions( context?: ProductDealMethodContext | null, isBuy = true, ): SelectItem[] { const metalId = Number(context?.productMetalId ?? 0) const catalog = context?.catalog ?? [] const cards = context?.inventoryCards ?? [] const tradedProductId = context?.tradedProduct?.id ?? null if (!metalId || !catalog.length) { return [] } const matchingProducts = catalog.filter((product) => isQuantityTradeCatalogProduct(product, metalId, tradedProductId) ) if (isBuy) { const options: SelectItem[] = [] for (const product of matchingProducts) { const card = findInventoryCardForProduct(cards, product.id) if (!card || Number(card.quantity ?? 0) <= 0) { continue } options.push({ key: buildMeltedDealMethodKey(product.id), value: `${product.title} — ${formatInventoryCardValue(card)}`, }) } return options } return matchingProducts.map((product) => { const card = findInventoryCardForProduct(cards, product.id) const inventorySuffix = card ? ` — ${formatInventoryCardValue(card)}` : '' return { key: buildMeltedDealMethodKey(product.id), value: `دریافت ${product.title}${inventorySuffix}`, } }) } /** @deprecated از getQuantityTradePaymentDealOptions استفاده کنید */ export function getMeltedPaymentDealOptions( context?: ProductDealMethodContext | null, isBuy = true, ) { return getQuantityTradePaymentDealOptions(context, isBuy) } export function findMeltedDealMethodCard( method?: string | null, cards: WalletInventoryCard[] = [], ) { return findInventoryCardForProduct(cards, parseMeltedDealMethodProductId(method)) } export function findProductDealMethodProduct( method?: string | null, catalog: Product[] = [], ) { const productId = parseMeltedDealMethodProductId(method) if (!productId) { return null } return catalog.find((product) => Number(product.id) === productId) ?? null } export function resolveProductTradeBuyUnitPrice(product?: Product | null) { if (!product) { return 0 } const basePrice = (product.price ?? 0) + (product.cash_fee_buy ?? 0) return basePrice * productPriceAmountFactor(product) } export function resolveProductTradeSellUnitPrice(product?: Product | null) { if (!product) { return 0 } const basePrice = (product.price ?? 0) - (product.cash_fee_sell ?? 0) return basePrice * productPriceAmountFactor(product) } function getDefaultTypeDealOptions(context?: ProductDealMethodContext | null, isBuy = true): SelectItem[] { const cashOption: SelectItem = { value: 'نقدی', key: 'cash' } if (!shouldShowProductDealMethodSelector(context?.tradedProduct ?? null)) { return [cashOption] } const quantityOptions = getQuantityTradePaymentDealOptions(context, isBuy) if (quantityOptions.length) { return [cashOption, ...quantityOptions] } return [cashOption] } export function getProductDealMethodOptions( groupType?: string | null, isBuy = true, context?: ProductDealMethodContext | null, ): SelectItem[] { const settingsStore = useSettingsStore() if (!isBoursecuProject()) { return getDefaultTypeDealOptions(context, isBuy) } if (isMaftolMesGroup(groupType)) { if (isBuy) { return [ { value: `پرداخت با ${settingsStore.metalTypeLabel}`, key: 'katod' }, { value: 'نقدی', key: 'cash' }, ] } return [ { value: 'نقدی', key: 'cash' }, { value: `دریافت ${settingsStore.metalTypeLabel}`, key: 'katod' }, ] } if (isKatodMesGroup(groupType)) { return [{ value: 'نقدی', key: 'cash' }] } if (isMeltedTradeGroup(groupType)) { return [{ value: settingsStore.meltedGoldLabel, key: 'gold' }] } return getDefaultTypeDealOptions(context, isBuy) } export function getDefaultProductDealMethod( groupType?: string | null, isBuy = true, context?: ProductDealMethodContext | null, ) { return getProductDealMethodOptions(groupType, isBuy, context)[0]?.key ?? 'cash' } export function getProductDealMethodPlaceholder(isBuy = true) { return isBuy ? 'نحوه خرید را انتخاب کنید' : 'نحوه دریافت را انتخاب کنید' } export function formatMainMetalReceiveText(weight?: number | null) { return `${useSettingsStore().metalTypeLabel}: ${weight ?? 0}${getWeightUnit()}` } export function calcSellReceivedMetalWeight( count?: number | null, product?: ProductUnitSource & { percent_fee_sell?: number | null } | null, ) { const quantity = Number(count ?? 0) const unitAmount = Number(product?.amount ?? 0) if (!quantity || !unitAmount) { return 0 } const grossAmount = quantity * unitAmount const feeRate = Number(product?.percent_fee_sell ?? 0) return grossAmount - feeRate * grossAmount } export function isCashLikeDealMethod(method?: string | null) { return method === 'cash' } export function isMainMetalWalletDealMethod(method?: string | null) { return method === 'gold' || method === 'katod' || isMeltedProductDealMethod(method) } export function getMeltedDealMethodBalance( method?: string | null, cards: WalletInventoryCard[] = [], mainWalletGold?: number | null, ) { const card = findMeltedDealMethodCard(method, cards) if (card) { return Number(card.quantity ?? 0) } if (method === 'gold' || method === 'katod') { return Number(mainWalletGold ?? 0) } return 0 } /** برچسب کیف فلز اصلی (main wallet gold) — از setting metal_type */ export function getMainMetalWalletLabel() { return `موجودی کیف ${useSettingsStore().metalTypeLabel}` } /** برچسب کیف آبشده — از setting metal_type */ export function getMeltedGoldWalletLabel() { return `موجودی کیف ${useSettingsStore().meltedGoldLabel}` } export function getBoursecuMetalWalletLabel(groupType?: string | null) { const group = normalizeProductGroupType(groupType) if (group === BOURSECU_GROUP_MAFTOL) { return BOURSECU_GROUP_MAFTOL } if (group === BOURSECU_GROUP_KATOD) { return BOURSECU_GROUP_KATOD } if (group === BOURSECU_GROUP_ABSHODE) { return useSettingsStore().meltedGoldLabel } return useSettingsStore().metalTypeLabel } export function getGoldWalletLabel(groupType?: string | null) { if (!isBoursecuProject()) { return getMainMetalWalletLabel() } const group = normalizeProductGroupType(groupType) if (group === BOURSECU_GROUP_MAFTOL || group === BOURSECU_GROUP_KATOD || group === BOURSECU_GROUP_ABSHODE) { return `موجودی کیف ${getBoursecuMetalWalletLabel(groupType)}` } if (!group) { return getMainMetalWalletLabel() } return BOURSECU_WALLET_LABEL_GENERIC } /** پرداخت نقدی همیشه از کیف پول ریالی — مستقل از نوع محصول */ export function getCashWalletLabel(_groupType?: string | null) { return 'موجودی کیف پول' } export function getMetalAssetLabel() { return `دارایی ${useSettingsStore().metalTypeLabel}` } export function getMeltedGoldAssetLabel() { return `دارایی ${useSettingsStore().meltedGoldLabel}` } export function getMaftolAssetLabel() { return `دارایی ${BOURSECU_GROUP_MAFTOL}` } export function getWeightUnit() { return useSettingsStore().tradeWeightUnit } export function formatBoursecuAssetAmount(amount?: number | null) { return `${amount ?? 0}${getWeightUnit()}` } export function formatMainMetalWalletBalanceText(gold: number) { return `${getMainMetalWalletLabel()}: ${gold} ${getWeightUnit()}` } export function formatGoldWalletBalanceText(gold: number, groupType?: string | null) { return `${getGoldWalletLabel(groupType)}: ${gold} ${getWeightUnit()}` } export function formatCashWalletBalanceText(cashFormatted: string | number, groupType?: string | null) { return `${getCashWalletLabel(groupType)}: ${cashFormatted} ریال` } /** برچسب کیف فلزی متناسب با نحوه پرداخت (مثلاً کاتد مس برای خرید مفتول با کیف کاتد) */ export function getMetalWalletLabelForDealMethod(method?: string | null, groupType?: string | null) { if (!isBoursecuProject() || method === 'katod') { return getMainMetalWalletLabel() } return getGoldWalletLabel(groupType) } export function getInsufficientGoldWalletMessage(groupType?: string | null, method?: string | null) { return `${getMetalWalletLabelForDealMethod(method, groupType)} ناکافی است` } export function getInsufficientCashWalletMessage(_groupType?: string | null) { return `${getCashWalletLabel()} ناکافی است` } export function getInsufficientMainMetalWalletMessage() { return `${getMainMetalWalletLabel()} ناکافی است` } export function getMeltedGoldTradeLabel() { return useSettingsStore().meltedGoldLabel } export function getMetalTypeTradeLabel() { return useSettingsStore().metalTypeLabel } export function getTradePriceUnitLabel() { return useSettingsStore().metalTypeLabel } export type TransactionTradeProductSource = { product?: (ProductUnitSource & { title?: string | null }) | null wallet?: { product?: (ProductUnitSource & { title?: string | null }) | null } | null title?: string | null } /** محصول معامله‌شده — نه لزوماً همان کیف پول (مثلاً خرید مفتول با کیف کاتد) */ export function resolveTransactionTradeProduct( item?: TransactionTradeProductSource | null, ): (ProductUnitSource & { title?: string | null }) | null { if (!item) { return null } const fromTransaction = item.product if ((fromTransaction?.title ?? '').trim()) { return fromTransaction ?? null } const title = (item.title ?? '').trim() const match = title.match(/^(?:خرید|فروش)\s+(.+)$/u) if (match?.[1]?.trim()) { return { ...(item.wallet?.product ?? {}), title: match[1].trim(), } } return item.wallet?.product ?? null } /** برچسب قیمت حین معامله — نام و واحد همان محصول معامله‌شده */ export function getTransactionTradePriceLabel(product?: (ProductUnitSource & { title?: string | null }) | null) { if (!product) { return `قیمت هر ${getWeightUnit()} ${getTradePriceUnitLabel()}` } const title = (product.title ?? '').trim() if (usesQuantityTrade(product)) { const unit = getProductQuantityUnit(product) return title ? `قیمت هر ${unit} ${title}` : `قیمت هر ${unit}` } if (title) { return `قیمت هر ${getWeightUnit()} ${title}` } return `قیمت هر ${getWeightUnit()} ${getTradePriceUnitLabel()}` } /** وزن کل زیر مقدار مبادله‌شده — فقط محصول تعدادی (نه trade_by_quantity) */ export function shouldShowTransactionTotalWeightLine(product?: ProductUnitSource | null) { if (!product || usesQuantityTrade(product) || !hasProductUnitWeight(product)) { return false } return isCountBasedProduct(product) }