Manual sync buttons pull customer payments and owner payouts into treasury, invoices, and accounting vouchers; voucher page gains category, date, status, and search filters; supplier vouchers dedupe against treasury-backed entries. Co-authored-by: Cursor <cursoragent@cursor.com>
119 lines
5.8 KiB
TypeScript
119 lines
5.8 KiB
TypeScript
import { Link } from 'react-router-dom'
|
||
import { useMemo } from 'react'
|
||
import { useStore } from '../../store/AppStore'
|
||
import { Card, StatCard } from '../../components/ui'
|
||
import { formatMoney, toFa } from '../../utils/format'
|
||
import { salesNavItems } from './salesNavigation'
|
||
import { salesDocuments, salesSummary } from './salesUtils'
|
||
import { FunZoneSalesSyncBanner } from './FunZoneSalesSyncBanner'
|
||
import { isFunZoneSalesInvoice } from './funzoneSalesSync'
|
||
|
||
const processSteps = [
|
||
{ step: 1, label: 'پیشفاکتور', desc: 'صدور پیشنهاد قیمت برای مشتری', to: '/sales/proforma', icon: '📋' },
|
||
{ step: 2, label: 'فاکتور فروش', desc: 'ثبت فروش قطعی و کاهش موجودی', to: '/sales/invoice', icon: '🧾' },
|
||
{ step: 3, label: 'دریافت', desc: 'تسویه از مشتری در ماژول دریافت و پرداخت', to: '/treasury', icon: '💰' },
|
||
{ step: 4, label: 'فاکتور برگشتی', desc: 'برداشت در انتظار مشتریان و برگشت کالا', to: '/sales/return', icon: '↩️' },
|
||
]
|
||
|
||
export function SalesProcess() {
|
||
const { data } = useStore()
|
||
const summary = useMemo(() => salesSummary(data), [data])
|
||
const funzoneSyncedCount = useMemo(
|
||
() => salesDocuments(data.invoices).filter(isFunZoneSalesInvoice).length,
|
||
[data.invoices],
|
||
)
|
||
|
||
const quickLinks = salesNavItems.filter((item) => item.to !== '/sales')
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<FunZoneSalesSyncBanner />
|
||
<Card className="p-5">
|
||
<h2 className="mb-1 text-lg font-bold text-slate-800">فرایند فروش</h2>
|
||
<p className="mb-6 text-sm text-slate-500">
|
||
جریان استاندارد فروش مشابه سپیدار: از پیشفاکتور تا فاکتور، دریافت و برگشت.
|
||
</p>
|
||
|
||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
||
{processSteps.map((step, idx) => (
|
||
<div key={step.step} className="relative">
|
||
<Link
|
||
to={step.to}
|
||
className="flex flex-col rounded-xl border border-slate-200 bg-slate-50/50 p-4 transition hover:border-brand-200 hover:bg-brand-50/30"
|
||
>
|
||
<div className="mb-2 flex items-center gap-2">
|
||
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-brand-100 text-xs font-bold text-brand-700">
|
||
{toFa(step.step)}
|
||
</span>
|
||
<span className="text-lg">{step.icon}</span>
|
||
</div>
|
||
<p className="font-semibold text-slate-800">{step.label}</p>
|
||
<p className="mt-1 text-xs text-slate-500">{step.desc}</p>
|
||
</Link>
|
||
{idx < processSteps.length - 1 && (
|
||
<span className="absolute -left-2 top-1/2 hidden -translate-y-1/2 text-slate-300 lg:block">←</span>
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
|
||
<div className="grid grid-cols-2 gap-4 lg:grid-cols-5">
|
||
<StatCard label="پیشفاکتور" value={toFa(summary.proformaCount)} hint={`${toFa(summary.openProformas)} باز`} />
|
||
<StatCard label="فاکتور فروش" value={toFa(summary.invoiceCount)} hint={formatMoney(summary.totalSales)} />
|
||
<StatCard label="فاکتور برگشتی" value={toFa(summary.returnCount)} hint={formatMoney(summary.totalReturns)} />
|
||
<StatCard label="فروش خالص" value={formatMoney(summary.netSales)} hint={`${toFa(summary.unpaidCount)} تسویهنشده`} />
|
||
<StatCard label="همگام فانزون" value={toFa(funzoneSyncedCount)} hint="فاکتور از پرداخت رویداد" tone="brand" />
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||
<Card className="p-5">
|
||
<h3 className="mb-3 font-bold text-slate-800">اتصال به سایر ماژولها</h3>
|
||
<ul className="space-y-2 text-sm text-slate-600">
|
||
<li className="flex items-start gap-2">
|
||
<span>📦</span>
|
||
<span>
|
||
<strong>انبارداری:</strong> فاکتور فروش قطعی موجودی را کاهش و فاکتور برگشتی آن را افزایش میدهد.
|
||
</span>
|
||
</li>
|
||
<li className="flex items-start gap-2">
|
||
<span>📑</span>
|
||
<span>
|
||
<strong>اسناد حسابداری:</strong> با تأیید فاکتور، سند درآمد/مطالبات بهصورت خودکار صادر میشود.
|
||
</span>
|
||
</li>
|
||
<li className="flex items-start gap-2">
|
||
<span>💰</span>
|
||
<span>
|
||
<strong>دریافت و پرداخت:</strong> دریافت از مشتری مانده حساب را کاهش میدهد.
|
||
</span>
|
||
</li>
|
||
<li className="flex items-start gap-2">
|
||
<span>⚖️</span>
|
||
<span>
|
||
<strong>اعلامیه بدهکار/بستانکار:</strong> برای اصلاح مانده مشتری بدون فاکتور.
|
||
</span>
|
||
</li>
|
||
</ul>
|
||
</Card>
|
||
|
||
<Card className="p-5">
|
||
<h3 className="mb-3 font-bold text-slate-800">دسترسی سریع</h3>
|
||
<div className="grid grid-cols-2 gap-2">
|
||
{quickLinks.map((item) => (
|
||
<Link
|
||
key={item.to}
|
||
to={item.to}
|
||
className="flex items-center gap-2 rounded-lg border border-slate-200 px-3 py-2.5 text-sm font-medium text-slate-700 transition hover:border-brand-200 hover:bg-brand-50/40"
|
||
>
|
||
<span>{item.icon}</span>
|
||
<span>{item.label}</span>
|
||
</Link>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|