import type { Product } from '@/models/products' import { usePriceWithoutType } from '@/utils/usePrice' import type { UserWalletLine } from '@/models/wallet' import type { ProductFile } from '@/utils/file' import type { WalletInventoryCard, WalletMetalTotal } from '@/models/walletAssets' import { getFileUrl } from '@/utils/file' import { isCountBasedProduct, getProductQuantityUnit, type ProductUnitSource } from '@/utils/productDealMethod' import { isMeltedTradeGroup, normalizeProductGroupType, LEGACY_GROUP_KATOD } from '@/utils/productCategories' import defaultImg from '@/assets/img/defaultAbshodeh.svg' import goyImg from '@/assets/img/goyImg.svg' import mokaabImg from '@/assets/img/mokaabImg.svg' import sekkeImg from '@/assets/img/sekkeImg.svg' import mesImg from '@/assets/img/mesImg.jpg' import noghrehImg from '@/assets/img/noghrehImg.webp' import parsianImg from '@/assets/img/parsianImg.svg' export const METAL_GOLD = 1 export const METAL_SILVER = 2 export const METAL_COPPER = 3 const GROUP_DEFAULT_IMAGES: Record = { گوی: goyImg, مکعب: mokaabImg, سکه: sekkeImg, مس: mesImg, [LEGACY_GROUP_KATOD]: mesImg, نقره: noghrehImg, 'سکه پارسیان': parsianImg, طلا: defaultImg, آبشده: defaultImg, } export function getMetalLabel(metalId: number): string { switch (metalId) { case METAL_GOLD: return 'طلا' case METAL_SILVER: return 'نقره' case METAL_COPPER: return 'مس' default: return 'فلز' } } export function getTotalMetalLabel(metalId: number): string { return `کل دارایی ${getMetalLabel(metalId)}` } /** boursecu home — ترتیب نمایش: مس، طلا، سایر فلزها */ export function resolveBoursecuAssetMetalTotals( metalTotals: WalletMetalTotal[] ): WalletMetalTotal[] { if (!metalTotals.length) { return [] } const ordered: WalletMetalTotal[] = [] const copper = metalTotals.find((metal) => metal.metal_id === METAL_COPPER) const gold = metalTotals.find((metal) => metal.metal_id === METAL_GOLD) if (copper) { ordered.push(copper) } if (gold) { ordered.push(gold) } for (const metal of metalTotals) { if (!ordered.some((item) => item.metal_id === metal.metal_id)) { ordered.push(metal) } } return ordered } export function resolveMetalAssetsDisplayLabel( metal: WalletMetalTotal, isBoursecuLayout: boolean ): string { if (!isBoursecuLayout) { return metal.label } switch (metal.metal_id) { case METAL_COPPER: return 'دارایی مس' case METAL_GOLD: return 'دارایی شمش مس' case METAL_SILVER: return 'دارایی نقره' default: return metal.label } } export type HomeAssetRowInfo = 'metal' | 'rial' | 'none' export interface HomeAssetRow { id: string label: string value: string metal?: WalletMetalTotal info: HomeAssetRowInfo /** boursecu — متن مودال توضیحات (info) */ modalText?: string /** boursecu — عنوان مودال (مثلاً کل دارایی مس) */ modalTitle?: string /** boursecu — محتوای چندخطی مودال */ modalContent?: string } function formatInventoryCardProductLabel(card: WalletInventoryCard): string { return `دارایی ${card.title}` } /** boursecu — متن مودال برای ردیف‌های metal_totals */ export function formatBoursecuMetalModalText(metal: WalletMetalTotal): string { switch (metal.metal_id) { case METAL_COPPER: return getTotalMetalLabel(METAL_COPPER) case METAL_GOLD: return 'دارایی شمش مس = موجودی شمش مس' case METAL_SILVER: return 'دارایی نقره = موجودی نقره' default: return `${resolveMetalAssetsDisplayLabel(metal, true)} = موجودی ${metal.label}` } } /** boursecu — جمع موجودی کاتد، مفتول و شمش از کارت‌های دارایی صفحه اصلی */ function sumBoursecuHomeProductInventories( inventoryCards: WalletInventoryCard[], countProducts: WalletInventoryCard[] ): number { let sum = 0 for (const card of inventoryCards) { if (card.display_type === 'count') { continue } sum += Number(card.quantity ?? 0) } for (const card of countProducts) { const quantity = Number(card.quantity ?? 0) const unitWeight = Number(card.unit_weight ?? 0) if (unitWeight > 0) { sum += quantity * unitWeight continue } if (card.unit?.includes('کیلو')) { sum += quantity } } return sum } type BoursecuModalProduct = { title: string quantity: number unit: string } function toBoursecuModalProduct(card: WalletInventoryCard): BoursecuModalProduct { return { title: card.title, quantity: Number(card.quantity ?? 0), unit: card.unit ?? (card.display_type === 'count' ? 'عدد' : 'کیلوگرم'), } } function resolveBoursecuMetalModalProduct( metal: WalletMetalTotal, countProducts: WalletInventoryCard[] ): BoursecuModalProduct { const countCard = countProducts.find((card) => Number(card.metal_id) === metal.metal_id) ?? countProducts.find((card) => card.title?.includes('شمش')) if (countCard) { return toBoursecuModalProduct(countCard) } if (metal.metal_id === METAL_GOLD) { return { title: 'شمش مس', quantity: Number(metal.total_weight ?? 0), unit: 'عدد', } } return { title: metal.label, quantity: Number(metal.total_weight ?? 0), unit: metal.unit ?? 'کیلوگرم', } } /** boursecu — جزئیات کل دارایی مس در مودال info */ export function formatBoursecuCopperAssetsModalContent( metal: WalletMetalTotal, inventoryCards: WalletInventoryCard[], countProducts: WalletInventoryCard[], product: BoursecuModalProduct ): string { const weightUnit = metal.unit || 'کیلوگرم' const productWeight = usePriceWithoutType( sumBoursecuHomeProductInventories(inventoryCards, countProducts), false ) const clickedQuantity = usePriceWithoutType(product.quantity, false) return [ `مجموع کیف محصولات: ${productWeight} ${weightUnit}`, `موجودی ${product.title}: ${clickedQuantity} ${product.unit}`, ].join('\n') } function withBoursecuCopperModal( row: Omit, copperTotal: WalletMetalTotal | undefined, inventoryCards: WalletInventoryCard[], countProducts: WalletInventoryCard[], product: BoursecuModalProduct ): HomeAssetRow { if (!copperTotal) { return row } return { ...row, metal: copperTotal, modalTitle: getTotalMetalLabel(METAL_COPPER), modalContent: formatBoursecuCopperAssetsModalContent( copperTotal, inventoryCards, countProducts, product ), } } function findMetalTotalById(metalTotals: WalletMetalTotal[], metalId: number) { return metalTotals.find((metal) => metal.metal_id === metalId) } function formatBoursecuMetalRowValue(metal: WalletMetalTotal): string { const amount = usePriceWithoutType(Number(metal.total_weight ?? 0), false) if (metal.metal_id === METAL_GOLD) { return `${amount} عدد` } return `${amount}${metal.unit ?? ''}` } /** boursecu home — هر محصول / فلز / ریال از customer/wallet/assets جدا نمایش داده می‌شود */ export function buildBoursecuHomeAssetRows( summary: { inventory_cards?: WalletInventoryCard[] count_products?: WalletInventoryCard[] metal_totals?: WalletMetalTotal[] total_rial_value?: number } | null | undefined ): HomeAssetRow[] { if (!summary) { return [] } const rows: HomeAssetRow[] = [] const metalTotals = summary.metal_totals ?? [] const inventoryCards = summary.inventory_cards ?? [] const countProducts = summary.count_products ?? [] const copperTotal = findMetalTotalById(metalTotals, METAL_COPPER) for (const card of inventoryCards) { if (card.display_type === 'count') { continue } rows.push( withBoursecuCopperModal( { id: `inventory-${card.product_id}`, label: formatInventoryCardProductLabel(card), value: formatInventoryCardValue(card), metal: findMetalTotalById(metalTotals, card.metal_id), info: 'metal', }, copperTotal, inventoryCards, countProducts, toBoursecuModalProduct(card) ) ) } for (const metal of metalTotals) { if (metal.metal_id === METAL_COPPER) { continue } rows.push( withBoursecuCopperModal( { id: `metal-${metal.metal_id}`, label: resolveMetalAssetsDisplayLabel(metal, true), value: formatBoursecuMetalRowValue(metal), metal, info: 'metal', }, copperTotal, inventoryCards, countProducts, resolveBoursecuMetalModalProduct(metal, countProducts) ) ) } for (const card of countProducts) { rows.push( withBoursecuCopperModal( { id: `count-${card.product_id}`, label: formatInventoryCardProductLabel(card), value: formatInventoryCardValue(card), metal: findMetalTotalById(metalTotals, card.metal_id), info: 'metal', }, copperTotal, inventoryCards, countProducts, toBoursecuModalProduct(card) ) ) } rows.push({ id: 'rial-total', label: 'دارایی ریالی', value: `${formatRialNumber(summary.cash_wallet ?? 0)} ریال`, info: 'rial', }) return rows } export function sumInventoryCardsWeight( cards: Array<{ metal_id?: number; quantity?: number }>, metalId: number ): number { return cards .filter((card) => Number(card.metal_id) === metalId) .reduce((sum, card) => sum + Number(card.quantity ?? 0), 0) } /** ردیف‌های جزئی action sheet — فقط محصولاتی که show_in_assets روشن است. */ export function assetSheetInventoryCards( cards: T[], metalId?: number ): T[] { return cards.filter((card) => { if (!card.show_in_assets) { return false } if (metalId == null) { return true } return Number(card.metal_id) === metalId }) } /** همه کارت‌های صفحه دارایی‌ها — بدون فیلتر show_in_assets. */ export function fundPageInventoryCards(cards: T[]): T[] { return cards.filter((card) => Number((card as { quantity?: number }).quantity ?? 0) > 0) } export function resolveSummaryMetalWeight( metal: { total_weight: number }, _cards?: Array<{ metal_id?: number; quantity?: number }> ): number { return Number(metal.total_weight ?? 0) } /** متن توضیح مودال دارایی فلزی — بر اساس metal_totals سرور، بدون هاردکد طلا */ export function formatMetalAssetsInfoText(metal: WalletMetalTotal): string { const unit = metal.unit || 'گرم' return `${metal.label}: مجموع کیف محصولات (${metal.product_weight} ${unit}) + موجودی کیف اصلی (${metal.melted_weight} ${unit})` } function formatRialNumber(value: number): string { return usePriceWithoutType(value) } /** متن توضیح مودال دارایی ریالی */ export function formatRialAssetsInfoText(cashWallet: number, metalRialValue: number): string { const total = cashWallet + metalRialValue return [ `کیف پول ریالی: ${formatRialNumber(cashWallet)} ریال`, `ارزش ریالی طلا (وزن × قیمت زنده): ${formatRialNumber(metalRialValue)} ریال`, `مجموع: ${formatRialNumber(total)} ریال`, ].join('\n') } /** ارزش ریالی فلزات از وزن × قیمت زنده — fallback کلاینت قبل از deploy بک‌اند */ export function computeMetalRialValue( metalTotals: Array<{ metal_id?: number; total_weight?: number }>, liveGoldPrice: number, livePricesByMetal?: Partial> ): number { return metalTotals.reduce((sum, metal) => { const weight = Number(metal.total_weight ?? 0) if (weight <= 0) { return sum } const metalId = Number(metal.metal_id ?? 0) const unitPrice = livePricesByMetal?.[metalId] ?? (metalId === METAL_GOLD ? liveGoldPrice : 0) return sum + weight * unitPrice }, 0) } export function resolveAssetsRialValue( summary: { assets_rial_value?: number; metal_totals?: Array<{ metal_id?: number; total_weight?: number }> } | null | undefined, liveGoldPrice: number ): number { const fromServer = Number(summary?.assets_rial_value ?? 0) if (fromServer > 0) { return fromServer } return computeMetalRialValue(summary?.metal_totals ?? [], liveGoldPrice) } export function formatInventoryCardValue(card: { quantity?: number unit?: string display_type?: string }): string { const quantity = Number(card.quantity ?? 0) const unit = card.unit ?? (card.display_type === 'count' ? 'عدد' : 'گرم') return `${usePriceWithoutType(quantity, false)} ${unit}` } export function findProductInventoryCard( cards: WalletInventoryCard[], productId?: number | null ): WalletInventoryCard | undefined { if (!productId) { return undefined } return cards.find((item) => Number(item.product_id) === Number(productId)) } /** * موجودی کیف یک محصول از inventory_cards. * fallback: پروژه‌های ساختار قدیم (مثل گلد علی) کیف محصول ندارند و موجودی روی gold کیف اصلی است. */ export function getProductInventoryQuantity( cards: WalletInventoryCard[], productId?: number | null, mainWalletGold?: number | null ): number { const card = findProductInventoryCard(cards, productId) if (card) { return Number(card.quantity ?? 0) } return Number(mainWalletGold ?? 0) } /** برچسب موجودی کیف محصول — برای فرم خرید/فروش */ export function formatProductWalletBalanceText( product: { id?: number; title?: string | null; unit?: string | null; group_type?: string | null } | null | undefined, cards: WalletInventoryCard[] = [], mainWalletGold?: number | null ): string { const card = findProductInventoryCard(cards, product?.id) const quantity = card ? Number(card.quantity ?? 0) : Number(mainWalletGold ?? 0) const unit = card?.unit ?? getProductQuantityUnit(product) const title = product?.title?.trim() || 'محصول' return `موجودی کیف ${title}: ${quantity} ${unit}` } export function isSingleMetalCatalog(metalIds: number[]): boolean { return metalIds.length === 1 } export function getCatalogMetalIds(products?: Product[] | null): number[] { const ids = new Set() for (const product of products ?? []) { const metalId = Number(product.metal_id) if (metalId > 0) { ids.add(metalId) } } return Array.from(ids).sort((a, b) => a - b) } export function catalogHasCountBasedProducts(products?: Product[] | null): boolean { return (products ?? []).some((product) => isCountBasedProduct(product)) } export function catalogHasMeltedProducts(products?: Product[] | null): boolean { return (products ?? []).some((product) => isMeltedTradeGroup(product.group_type)) } export function enrichInventoryCardsWithCatalog( cards: T[], catalog: Product[] ): T[] { return cards.map((card) => { const fromCatalog = catalog.find((product) => product.id === card.product_id) if (!fromCatalog) { return card } return { ...card, file: card.file ?? fromCatalog.file ?? null, } }) } export function enrichInventoryWithCatalog( items: T[], catalog: Product[] ): T[] { return items.map((item) => { const fromCatalog = catalog.find((product) => product.id === item.id) if (!fromCatalog) { return item } const current = item as T & Product return { ...fromCatalog, ...current, wallet: current.wallet ?? fromCatalog.wallet, file: current.file ?? fromCatalog.file, metal_id: current.metal_id ?? fromCatalog.metal_id, group_type: current.group_type ?? fromCatalog.group_type, } }) } export function getProductDisplayImage(product?: { group_type?: string | null title?: string | null file?: ProductFile | null img?: string | null } | null): string { const fileUrl = getFileUrl(product?.file) if (fileUrl) { return fileUrl } if (product?.img && /^https?:\/\//i.test(product.img)) { return product.img } const group = normalizeProductGroupType(product?.group_type) if (group && GROUP_DEFAULT_IMAGES[group]) { return GROUP_DEFAULT_IMAGES[group] } return defaultImg } type InventoryProduct = ProductUnitSource & { metal_id?: number | null title?: string | null wallet?: { gold?: number | null } | null } export function sumCountBasedInventory(products?: InventoryProduct[] | null): number { return (products ?? []) .filter((product) => product.title !== 'آبشده نقدی' && isCountBasedProduct(product)) .reduce((sum, product) => sum + Number(product.wallet?.gold ?? 0), 0) } export function sumProductWalletByMetal( products: InventoryProduct[] | null | undefined, metalId: number ): number { return (products ?? []) .filter((product) => Number(product.metal_id) === metalId) .reduce((sum, product) => sum + Number(product.wallet?.gold ?? 0), 0) } export function getMainWalletLine(wallets?: UserWalletLine[] | null): UserWalletLine | undefined { return wallets?.find((line) => line.product_id == null) } export function getMainWalletGold(wallets?: UserWalletLine[] | null): number { return Number(getMainWalletLine(wallets)?.gold ?? 0) } export function getMainWalletCash(wallets?: UserWalletLine[] | null): number { return Number(getMainWalletLine(wallets)?.cash ?? 0) } export function sumWalletsGold( wallets?: UserWalletLine[] | null, metalId?: number | null ): number { if (!wallets?.length) { return 0 } return wallets.reduce((sum, line) => { const weight = Number(line.gold ?? 0) if (metalId == null) { return sum + weight } if (line.product_id == null) { return metalId === METAL_GOLD ? sum + weight : sum } return Number(line.product?.metal_id) === metalId ? sum + weight : sum }, 0) } export function sumProductWalletsGold( wallets?: UserWalletLine[] | null, metalId?: number | null ): number { return (wallets ?? []) .filter((line) => line.product_id != null) .reduce((sum, line) => { const weight = Number(line.gold ?? 0) if (metalId == null) { return sum + weight } return Number(line.product?.metal_id) === metalId ? sum + weight : sum }, 0) } export function sumInventoryMetalWeight(products?: InventoryProduct[] | null): number { return (products ?? []) .filter((product) => product.title !== 'آبشده نقدی') .reduce((sum, product) => sum + Number(product.wallet?.gold ?? 0), 0) } export function getTotalMetalWeight( walletInfo?: Record | null, wallets?: UserWalletLine[] | null, inventoryProducts?: InventoryProduct[] | null ): number { const fromInventory = sumInventoryMetalWeight(inventoryProducts) if (fromInventory > 0) { return fromInventory } const fromProductWallets = sumProductWalletsGold(wallets) if (fromProductWallets > 0) { return fromProductWallets } if (wallets?.length) { return getMainWalletGold(wallets) } return Number(walletInfo?.treasure ?? walletInfo?.gold_sum ?? walletInfo?.count_gold ?? 0) } /** موجودی ریالی کیف اصلی — با main_wallet_products فیلد cash ممکن است کیف محصول باشد */ export function getRialCashAmount( walletInfo?: Record | null, wallets?: UserWalletLine[] | null ): number { const aggregated = walletInfo?.count_cash_wallet ?? walletInfo?.count_cash if (aggregated != null && aggregated !== '') { const parsed = Number(aggregated) if (!Number.isNaN(parsed)) { return parsed } } const mainCash = getMainWalletCash(wallets) if (mainCash > 0) { return mainCash } return Number(walletInfo?.cash ?? 0) } export function getCashWalletBalance( walletInfo?: Record | null, wallets?: UserWalletLine[] | null ): number { return getRialCashAmount(walletInfo, wallets) } export function getTotalRialEquivalent(walletInfo?: Record | null): number { return Number(walletInfo?.count_cash_total ?? 0) } export function getMetalAssetAmount( walletInfo: Record | null | undefined, metalId: number, inventoryProducts?: InventoryProduct[] | null ): number { if (inventoryProducts?.length) { const fromProducts = sumProductWalletByMetal(inventoryProducts, metalId) if (fromProducts > 0 || inventoryProducts.some((product) => Number(product.metal_id) === metalId)) { return fromProducts } } if (!walletInfo) { return 0 } switch (metalId) { case METAL_GOLD: return Number(walletInfo.count_gold_wallets ?? walletInfo.count_gold ?? 0) case METAL_SILVER: return Number(walletInfo.count_silver ?? 0) case METAL_COPPER: return Number(walletInfo.count_copper ?? 0) default: return 0 } } export function getMetalAssetAmountForDisplay( walletInfo: Record | null | undefined, metalId: number, catalogMetalIds: number[], inventoryProducts?: InventoryProduct[] | null, wallets?: UserWalletLine[] | null ): number { if (isSingleMetalCatalog(catalogMetalIds)) { return getTotalMetalWeight(walletInfo, wallets, inventoryProducts) } const productWeight = sumProductWalletByMetal(inventoryProducts, metalId) const mainWalletWeight = wallets?.length ? getMainWalletGold(wallets) : Number(walletInfo?.count_gold_wallet ?? 0) if (catalogHasMeltedProducts(inventoryProducts as Product[] | null | undefined) && metalId === catalogMetalIds[0]) { return productWeight + mainWalletWeight } if (wallets?.length) { const walletWeight = sumWalletsGold(wallets, metalId) if (walletWeight > 0) { return walletWeight } } return productWeight || getMetalAssetAmount(walletInfo, metalId, inventoryProducts) }