'use client'; import { useState, useEffect, useCallback } from 'react'; import { useAuth } from '@/contexts/AuthContext'; import { purchasesApi } from '@/lib/api/purchases'; import { packagesApi } from '@/lib/api/packages'; import { productsApi } from '@/lib/api/products'; import PageHeader from '@/components/admin/PageHeader'; import PersianDatePicker from '@/components/admin/PersianDatePicker'; import { Purchase, PAYMENT_GATEWAYS } from '@/types/purchase'; import { PackageName } from '@/types/package'; import { Product } from '@/types/product'; import { formatPrice, getPurchaseFinalPrice, parseStoreRate, parseFee } from '@/lib/utils'; import { MagnifyingGlassIcon, ArrowPathIcon, CalculatorIcon, BanknotesIcon, WalletIcon, ChartPieIcon, } from '@heroicons/react/24/outline'; // A single paid sale = success (موفق) or consumed (مصرف‌شده). // مایکت/کافه‌بازار تراکنش را مستقیماً با وضعیت «مصرف‌شده» ثبت می‌کنند (نه «موفق»)، // بنابراین هر دو وضعیت باید به‌عنوان فروش محسوب شوند. const PAID_STATUSES = [2, 3]; interface StoreSummary { count: number; gross: number; rate: number; fee: number; net: number; } interface ProductRow { productId: number | null; title: string; packageName: string; count: number; gross: number; commission: number; net: number; } interface AccountingResult { rows: ProductRow[]; totals: { count: number; gross: number; commission: number; net: number }; myket: StoreSummary; cafe: StoreSummary; other: StoreSummary; } // خلاصه فروش هر محصول و هر فروشگاه با احتساب کارمزد const computeAccounting = ( purchases: Purchase[], myketRate: number, cafeRate: number, transactionFee: number, ): AccountingResult => { const rows = new Map(); let totalCount = 0; let totalGross = 0; let totalCommission = 0; // درصد سهم توسعه‌دهنده به‌صورت کسر (0.85) و کارمزد تراکنش به ریال const makeStore = (rate: number): StoreSummary => ({ count: 0, gross: 0, rate, fee: 0, net: 0 }); const myket: StoreSummary = makeStore(myketRate); const cafe: StoreSummary = makeStore(cafeRate); const other: StoreSummary = makeStore(0); // سهم توسعه‌دهنده برای مایکت/کافه‌بازار: // (P / 1.1 - T) × R // P = قیمت فروش (با احتساب ۱۰٪ مالیات بر ارزش افزوده) // /1.1 = حذف ارزش افزوده، T = کارمزد تراکنش به ریال، R = درصد سهم const developerShare = (price: number, rate: number, feePerTransaction: number) => { const beforeVat = price / 1.1; const afterVatAndFee = beforeVat - feePerTransaction; if (afterVatAndFee <= 0) return 0; return afterVatAndFee * (rate / 100); }; const collect = (store: StoreSummary, price: number, rate: number, feePerTransaction: number) => { let net: number; if (rate > 0) { net = developerShare(price, rate, feePerTransaction); } else { net = price; // سایر درگاه‌ها: بدون کارمزد فروشگاه } store.count += 1; store.gross += price; store.fee += price - net; store.net += net; }; for (const purchase of purchases) { const price = getPurchaseFinalPrice(purchase); const gateway = purchase.gateway; let store: StoreSummary; let rate: number; let feePerTransaction: number; if (gateway === PAYMENT_GATEWAYS.cafe.code) { store = cafe; rate = cafeRate; feePerTransaction = transactionFee; } else if (gateway === PAYMENT_GATEWAYS.myket.code) { store = myket; rate = myketRate; feePerTransaction = transactionFee; } else { store = other; rate = 0; feePerTransaction = 0; } collect(store, price, rate, feePerTransaction); totalCount += 1; totalGross += price; totalCommission += price - developerShare(price, rate, feePerTransaction); const productId = purchase.product_id ?? purchase.product?.id ?? -1; let row = rows.get(productId); if (!row) { row = { productId, title: purchase.product?.title || 'نامشخص', packageName: purchase.product?.package_name?.title || purchase.product?.package_name?.name || '—', count: 0, gross: 0, commission: 0, net: 0, }; rows.set(productId, row); } row.count += 1; row.gross += price; row.commission += price - developerShare(price, rate, feePerTransaction); row.net += developerShare(price, rate, feePerTransaction); } return { rows: Array.from(rows.values()), totals: { count: totalCount, gross: totalGross, commission: totalCommission, net: totalGross - totalCommission, }, myket, cafe, other, }; }; export default function AccountingPage() { const { token } = useAuth(); // Package / product filters const [packages, setPackages] = useState([]); const [selectedPackageName, setSelectedPackageName] = useState(''); const [products, setProducts] = useState([]); const [selectedProductId, setSelectedProductId] = useState(''); // Date range const [dateFrom, setDateFrom] = useState(''); const [dateTo, setDateTo] = useState(''); // Status filter — both paid statuses selected by default const [statuses, setStatuses] = useState([...PAID_STATUSES]); // درصد سهم توسعه‌دهنده (R در فرمول) — مایکت و کافه‌بازار به‌طور پیش‌فرض ۸۵٪ const [myketRate, setMyketRate] = useState('85'); const [cafeRate, setCafeRate] = useState('85'); // کارمزد تراکنش به ریال (T در فرمول سهم توسعه‌دهنده) const [transactionFee, setTransactionFee] = useState('1200'); const [allPurchases, setAllPurchases] = useState(null); const [result, setResult] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const inputClass = 'block w-full px-3 py-2.5 text-gray-900 dark:text-gray-100 border border-gray-200 dark:border-gray-700 rounded-xl shadow-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent sm:text-sm dark:bg-gray-800/80 transition-colors'; const labelClass = 'block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5'; // Load packages once on mount useEffect(() => { if (!token) return; packagesApi .getAllPackages(token) .then((data) => { setPackages(data); if (data.length > 0) setSelectedPackageName(data[0].name || ''); }) .catch(() => setError('خطا در دریافت لیست پکیج‌ها')); }, [token]); // Load products when a package is selected useEffect(() => { if (!token || !selectedPackageName) { setProducts([]); setSelectedProductId(''); return; } setSelectedProductId(''); productsApi .getProducts(selectedPackageName, token) .then(setProducts) .catch(() => setProducts([])); }, [selectedPackageName, token]); const handleAnalyze = useCallback(async () => { if (!token) return; setIsLoading(true); setError(null); try { // Fetch all purchases once, then filter/aggregate in-memory. const purchases = await purchasesApi.getAllPurchases({ per_page: 500 }, token); setAllPurchases(purchases); setResult( computeAccounting( purchases, parseStoreRate(myketRate), parseStoreRate(cafeRate), parseFee(transactionFee) ) ); } catch (err) { setError(err instanceof Error ? err.message : 'خطا در دریافت داده‌ها'); } finally { setIsLoading(false); } }, [token, myketRate, cafeRate, transactionFee]); // Recompute whenever filters or commission rates change (uses cached data) const runAnalysis = useCallback(() => { if (!allPurchases) return; const myket = parseStoreRate(myketRate); const cafe = parseStoreRate(cafeRate); const fee = parseFee(transactionFee); const filtered = allPurchases.filter((p) => { // Status if (statuses.length > 0 && !statuses.includes(p.status)) return false; // Product if (selectedProductId !== '' && String(p.product_id) !== selectedProductId) return false; // Package (via nested product) if (selectedPackageName) { const pkgName = p.product?.package_name?.name || p.product?.package_name?.title || ''; if (pkgName && pkgName !== selectedPackageName) return false; } // Date range (created_at is "Y-m-d H:i:s") if (dateFrom && p.created_at && p.created_at.slice(0, 10) < dateFrom) return false; if (dateTo && p.created_at && p.created_at.slice(0, 10) > dateTo) return false; return true; }); setResult(computeAccounting(filtered, myket, cafe, fee)); }, [allPurchases, statuses, selectedProductId, selectedPackageName, dateFrom, dateTo, myketRate, cafeRate, transactionFee]); // Recompute on filter/rate changes (no refetch needed once data is cached) useEffect(() => { if (allPurchases) runAnalysis(); }, [runAnalysis, allPurchases]); const handleReset = () => { setSelectedPackageName(packages[0]?.name || ''); setSelectedProductId(''); setDateFrom(''); setDateTo(''); setStatuses([...PAID_STATUSES]); setMyketRate('85'); setCafeRate('85'); setTransactionFee('1200'); }; const toggleStatus = (code: number) => { setStatuses((prev) => prev.includes(code) ? prev.filter((c) => c !== code) : [...prev, code] ); }; const statCardClass = 'rounded-2xl bg-white dark:bg-gray-900 ring-1 ring-gray-200 dark:ring-gray-800 p-5 shadow-sm'; const statIconClass = 'h-5 w-5'; const statValueClass = 'text-2xl font-bold text-gray-900 dark:text-gray-100 mt-1.5'; return (
بارگذاری مجدد ) : null } /> {/* Filters */}
{ e.preventDefault(); handleAnalyze(); }} className="bg-white dark:bg-gray-900 ring-1 ring-gray-200 dark:ring-gray-800 rounded-2xl p-5 sm:p-6 mb-6 shadow-sm" >
{/* Status checkboxes */}
{[ { code: 2, label: 'موفق' }, { code: 3, label: 'مصرف‌شده' }, { code: 1, label: 'در انتظار پرداخت' }, ].map(({ code, label }) => ( ))}
{/* Commission settings */}
setMyketRate(e.target.value)} className={inputClass} placeholder="۸۵" />
setCafeRate(e.target.value)} className={inputClass} placeholder="۸۵" />
setTransactionFee(e.target.value)} className={inputClass} placeholder="۱۲۰۰" />

سهم توسعه‌دهنده = (قیمت ÷ ۱٫۱ − کارمزد تراکنش) × درصد سهم. پیش‌فرض مایکت و کافه‌بازار ۸۵٪ (فروش زیر ۱۰ میلیارد ریال) و ۷۰٪ (بالای آن) است.

{error && (

{error}

)} {isLoading && (
)} {!isLoading && allPurchases && result && ( <> {/* Summary stat cards */}
تعداد فروش
{formatPrice(result.totals.count)}
فروش ناخالص
{formatPrice(result.totals.gross)} تومان
کارمزد فروشگاه‌ها
{formatPrice(result.totals.commission)} تومان
سود خالص
{formatPrice(result.totals.net)} تومان
{/* Per-store breakdown */}
{/* Per-product table */}

تحلیل به تفکیک محصول

{result.rows.length === 0 ? (

هیچ فروشی با این فیلترها یافت نشد

) : (
{result.rows.map((row) => ( ))}
محصول پکیج تعداد فروش فروش ناخالص کارمزد فروشگاه سود خالص
{row.title} {row.packageName} {formatPrice(row.count)} {formatPrice(row.gross)} تومان {formatPrice(row.commission)} تومان {formatPrice(row.net)} تومان
)}
)} {!isLoading && !allPurchases && (

برای تحلیل فروش، فیلترها را تنظیم و روی «تحلیل» کلیک کنید

)}
); } function StoreCard({ title, summary }: { title: string; summary: StoreSummary }) { return (

{title}

{summary.rate > 0 && ( سهم توسعه‌دهنده {summary.rate}٪ )}
تعداد فروش {formatPrice(summary.count)}
فروش ناخالص {formatPrice(summary.gross)} تومان
سهم فروشگاه‌ها {formatPrice(summary.fee)} تومان
سود خالص {formatPrice(summary.net)} تومان
); }