diff --git a/src/composables/useWalletAssets.ts b/src/composables/useWalletAssets.ts index 08ed987..ad20133 100644 --- a/src/composables/useWalletAssets.ts +++ b/src/composables/useWalletAssets.ts @@ -17,6 +17,7 @@ export function useWalletAssets() { return assetsPromise } + // سرور مرجع دارایی‌هاست (main_wallet_products هم سمت سرور هندل می‌شود)؛ کلاینت فقط نمایش می‌دهد assetsPromise = Promise.all([getWalletAssets(), getProducts()]) .then(([data, catalog]) => { assetsSummary.value = { diff --git a/src/models/products.ts b/src/models/products.ts index 985d293..22cc688 100644 --- a/src/models/products.ts +++ b/src/models/products.ts @@ -12,6 +12,7 @@ export interface Product { unit: string active: boolean show: boolean + show_in_assets?: boolean open_sell: boolean open_buy: boolean group_type:string diff --git a/src/models/walletAssets.ts b/src/models/walletAssets.ts index f7827c1..7b5c691 100644 --- a/src/models/walletAssets.ts +++ b/src/models/walletAssets.ts @@ -20,6 +20,8 @@ export interface WalletInventoryCard { unit_weight?: string | null show_in_assets: boolean is_melted?: boolean + /** محصول متصل به کیف اصلی (main_wallet_products) — موجودی = gold کیف اصلی */ + is_main_wallet?: boolean file?: ProductFile | null } diff --git a/src/utils/price.ts b/src/utils/price.ts index 88df9b3..3749c94 100644 --- a/src/utils/price.ts +++ b/src/utils/price.ts @@ -2,7 +2,10 @@ import { getLastPrice, getPrice } from '@/api/home' import type { Price, PriceUp } from '@/models/home' import { ref } from 'vue' -let interval: ReturnType | undefined +const LAST_INTERVAL_MS = 10000 + +let lastInterval: ReturnType | undefined +let lastSubscribers = 0 /** @deprecated از lastPrice استفاده کنید — برای سازگاری با کد قدیمی sync می‌شود */ export const price = ref() @@ -23,18 +26,38 @@ export function normalizePriceResponse(data: Price): Price { return data } +/** polling /api/price/last — فقط صفحه اصلی */ +export function startLastPricePolling() { + lastSubscribers++ + if (lastInterval) { + return + } + lastInterval = setInterval(() => { + void getLastPrices() + }, LAST_INTERVAL_MS) +} + +/** @deprecated از startLastPricePolling استفاده کنید */ export async function setIntervalGetAllPrice() { - try { - interval = setInterval(async () => { - await getLastPrices() - }, 10000) - } catch (e) { - console.log(e) + startLastPricePolling() +} + +/** @deprecated از startLastPricePolling استفاده کنید */ +export async function setIntervalGetLastPrice() { + startLastPricePolling() +} + +export function stopLastPricePolling() { + lastSubscribers = Math.max(0, lastSubscribers - 1) + if (lastSubscribers === 0 && lastInterval) { + clearInterval(lastInterval) + lastInterval = undefined } } -export async function setIntervalGetLastPrice() { - return setIntervalGetAllPrice() +/** @deprecated از stopLastPricePolling استفاده کنید */ +export function stopPriceInterval() { + stopLastPricePolling() } /** @deprecated فقط getLastPrices را صدا بزنید */ @@ -79,13 +102,6 @@ export async function getLastPrices() { } } -export function stopPriceInterval() { - if (interval) { - clearInterval(interval) - interval = undefined - } -} - export function percentPriceChanged() { if (!olderPrice.value) { return 0 diff --git a/src/utils/productAssets.ts b/src/utils/productAssets.ts index 5aea38e..fb2cd43 100644 --- a/src/utils/productAssets.ts +++ b/src/utils/productAssets.ts @@ -1,8 +1,9 @@ import type { Product } from '@/models/products' 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, type ProductUnitSource } from '@/utils/productDealMethod' +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' @@ -54,16 +55,47 @@ export function sumInventoryCardsWeight( .reduce((sum, card) => sum + Number(card.quantity ?? 0), 0) } -export function resolveSummaryMetalWeight( - metal: { metal_id: number; total_weight: number }, - cards: Array<{ metal_id?: number; quantity?: number }> -): number { - const cardWeight = sumInventoryCardsWeight(cards, metal.metal_id) - if (cardWeight > 0) { - return Number(cardWeight.toFixed(3)) - } +/** ردیف‌های جزئی 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 + }) +} - return metal.total_weight +/** همه کارت‌های صفحه دارایی‌ها — بدون فیلتر 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 Math.round(value).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') +} + +/** متن توضیح مودال دارایی ریالی — cash_wallet + assets_rial_value */ +export function formatRialAssetsInfoText(cashWallet: number, assetsRialValue: number): string { + return `مجموع کیف پول ریالی (${formatRialNumber(cashWallet)} ریال) + معادل ریالی محصولات (${formatRialNumber(assetsRialValue)} ریال)` } export function formatInventoryCardValue(card: { @@ -76,6 +108,48 @@ export function formatInventoryCardValue(card: { return `${quantity} ${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 } diff --git a/src/views/base/copper&silver/index.vue b/src/views/base/copper&silver/index.vue index 96b2c0c..a299c29 100644 --- a/src/views/base/copper&silver/index.vue +++ b/src/views/base/copper&silver/index.vue @@ -204,7 +204,7 @@