نرمافزار مدیریت تعمیرگاه موبایل با گردشکار چندتخصصی (سختافزار / نرمافزار / تخصصی برد و هارد)، چندمستأجری (چند مغازه روی یک سیستم، با جداسازی کامل دادهها)، و ارسال خودکار پیامک هنگام آماده شدن دستگاه.
✅ آماده:
-
مدل دیتابیس کامل (مغازه، کاربران با نقش، مشتری، تیکت تعمیر، تاریخچه گردشکار، انبار، فاکتور)
-
احراز هویت (ورود با شماره موبایل + رمز عبور)، هر کاربر فقط به دادههای مغازه خودش دسترسی دارد
-
API کامل: پذیرش دستگاه، شروع کار، ارجاع بین بخشها، تکمیل و ارسال پیامک، تحویل، انبار، فاکتور
-
داشبورد Kanban تیکتها (صفحه
/tickets) — کاملاً وصل به API واقعی -
صفحه انبار قطعات (
/inventory) -
صفحه صدور و تاریخچه فاکتور (
/invoices) — انتخاب تیکت آمادهتحویل، افزودن قطعات از انبار، محاسبه خودکار جمع کل -
پنل مدیریت (
/admin, فقط برای نقش OWNER) — افزودن کارمند با نقش تخصصی، گزارش بهرهوری و درآمد هر تعمیرکار، درآمد ۳۰ روز اخیر مغازه -
بازار سراسری تعمیرکاران (
/market) — پستِ درخواست/پیشنهاد برد، فایل فلش، قطعه یا مشاوره تخصصی، قابل مشاهده توسط همه تعمیرکاران عضو در سراسر ایران، با فیلتر استان/شهر/دسته و پاسخدهی + تماس مستقیم -
ثبتنام عمومی (
/signup) — هر مغازه خودش میتواند ثبتنام کند و روی پلن رایگان شروع کند -
پرداخت و ارتقای پلن (
/admin/billing) — اتصال واقعی به درگاه زرینپال، ارتقا به پلن حرفهای/تجاری -
پنل مدیریت پلتفرم برای شما (
/superadmin, لاگین جدا در/superadmin/login) — لیست همه مغازههای مشترک، درآمد کل از اشتراکها، امکان تعلیق/فعالسازی هر مغازه. این پنل کاملاً از پنل/admin(مخصوص مدیر هر مغازه) جداست و با یک نوع لاگین متفاوت (PlatformAdmin) کار میکند -
محدودیت سهمیه ماهانه بر اساس پلن (رایگان: ۱۰، حرفهای: ۲۰۰، تجاری: عملاً نامحدود)
-
دکمه خروج از سیستم و حالت شب/روز (تم روشن و تیره، ذخیره در مرورگر کاربر)
-
پشتیبانی PWA پایه — قابل نصب روی صفحه اصلی گوشی، آیکون و صفحه شروع سفارشی، کش پایه برای اتصال ناپایدار
-
پرونده گوشی با IMEI (
/device-lookup) — جستجوی سراسری تاریخچه تعمیر یک دستگاه در همه مغازههای عضو، بهعلاوه:- گزارش هشدار جمعی (مسروقه / بدهی قسط) —
⚠️ این یک استعلام رسمی و قانونی نیست، صرفاً گزارشهای ثبتشده توسط سایر تعمیرکاران/فروشندگان است و باید همیشه با همین توضیح به کاربر نمایش داده شود - زنجیره خرید و فروش — فروشندگان میتوانند معاملات (فروشنده → خریدار، قیمت، تاریخ) را روی یک IMEI ثبت کنند
- مدل
Shop.typeاضافه شده (REPAIR|DEALER|BOTH) برای توسعه بعدی داشبورد جداگانه فروشندگان
- گزارش هشدار جمعی (مسروقه / بدهی قسط) —
-
بازیابی رمز عبور با کد پیامکی (
/forgot-password) — کد ۵ رقمی به شماره موبایل ارسال میشود، معتبر ۱۰ دقیقه -
آدرس مغازه — قابل ثبت در فرم ثبتنام یا بعداً از پنل مدیریت → اطلاعات مغازه
-
چت مستقیم مثل دیوار (
/chats) — روی هر آگهی بازار سراسری دکمه «ارسال پیام»؛ گفتگوی خصوصی بین خریدار و فروشنده، جدا از کامنتهای عمومی. بهروزرسانی هر ۴ ثانیه (polling ساده، بدون WebSocket) -
پنل مدیریت پلتفرم تکمیلتر شد: جستجو/فیلتر مغازهها بر اساس نام و پلن، آمار مشترکین فعال/پولی
-
پاس گرافیکی سراسری: سایه و عمق بصری روی کارتها، انیمیشن هاور دکمهها، اسکرولبار سفارشی
-
هویت بصری اختصاصی «میز تعمیر»: پسزمینه با الگوی ظریف نقطهچین شبیه برد مدار (PCB)، تیتر صفحات با گرادیانت مسی، و کارتهای تیکت بهشکل «برچسب تعمیر واقعی» (با نقطهی سوراخ و رنگ لبه بر اساس وضعیت: مسی=در صف، کهربایی=در حال انجام، فیروزهای=آماده تحویل)
-
پنل کارمندان فیلترشده: تعمیرکاران تخصصی (سختافزار/نرمافزار/برد) فقط تیکتهای خودشان (اختصاصیافته یا در صف بخش خودشان) را میبینند، نه کل تیکتهای مغازه. مدیر و پذیرش همچنان همهچیز را میبینند
-
کد ملی و تاریخ تولد در پروفایل هر کاربر (
/profile) -
نوار بالا: لینک اول با برچسب «🏠 صفحه اصلی» و ظاهر متمایز (رنگ مسی برجسته) بهجای «تیکتها»
-
دریافت رمز گوشی از مشتری در فرم پذیرش (فقط با تیک فعال میشود؛ برای تست بعد از تعمیر، فقط برای کارکنان همان مغازه قابل مشاهده است)
-
توضیحات مشتری درباره آسیبدیدگی یا تعمیر قبلی بهعنوان یک فیلد جدا در پذیرش
-
سه گزینه تأیید پذیرش در انتهای فرم: رسید چاپی مغازه امضا شد / رسید چاپی سایت امضا شد / بدون امضا و بدون فیش پذیرش شد
-
ویرایش کارمند توسط مدیر — نام، نقش، و فعال/غیرفعال بودن حساب هر کارمند از پنل مدیریت قابل تغییر است
-
نوع رمز گوشی قابل انتخاب (پین/پسورد/الگو) — اگر «الگو» انتخاب شود، یک صفحهی کشیدن الگو (شبیه قفل اندروید) نمایش داده میشود
-
صفحه سابقه و جستجو (
/history) — جستجو بر اساس نام/شماره مشتری، مدل دستگاه، IMEI، وضعیت، یا بازه تاریخ -
ثبتنام کاملتر مغازه: نوع کسبوکار (تکنفره/تیمی/مجموعه بزرگ)، تخصصهای ارائهشده، تلفن ثابت، کد ملی و تاریخ تولد مدیر
-
اشتراک ۱/۳/۶/۱۲ ماهه با تخفیف پلکانی (۵٪ تا ۲۰٪ برای مدتهای بلندتر)
-
سطحبندی احراز هویت (۱ تا ۳) — سطح ۱ بهمحض ثبتنام، سطح ۲ با تکمیل پروفایل و درخواست مدیر مغازه، سطح ۳ با تأیید پلتفرم از
/superadmin/verification -
انبار قطعات حالا ویرایش و حذف کامل دارد (حذفِ قطعهای که در فاکتور استفاده شده، بهجای حذف کامل، موجودی را صفر میکند تا تاریخچه مالی خراب نشود)
-
تغییر رمز عبور برای هر کاربر (مدیر یا کارمند) از صفحه پروفایل، با تأیید رمز فعلی
-
رفع باگ نوتیفیکیشن: دیگر با باز کردن زنگوله، همه بهصورت خودکار خواندهشده علامت نمیخورند — هر اعلان با کلیک روی خودش خوانده میشود، یا دکمه صریح «علامتگذاری همه»
-
کد QR پذیرش مشتری (
/kiosk/[shopId]) — مشتری با اسکن، مشخصات دستگاه خودش را ثبت میکند؛ کارمند از صفحه «پذیرش QR» تأیید یا رد میکند، تأیید خودکار تیکت واقعی میسازد -
امتیازدهی مشتری + رتبهبندی سراسری (
/rankings) — بعد از تحویل دستگاه، لینک امتیازدهی به مشتری پیامک میشود؛ رتبهبندی بر اساس میانگین امتیاز، قابل فیلتر بر اساس استان یا سراسر کشور (حداقل ۳ امتیاز برای ورود به رتبهبندی) -
تبلیغات پاپآپ علاوه بر بنر معمولی — قابل بستن، فقط یکبار در هر نشست نمایش داده میشود
-
ثبتنام با تب سربرگ برای انتخاب نوع کسبوکار (تکنفره/تیمی/مجموعه بزرگ)
-
نوار بالا بازطراحی شد — دستهبندی در منوهای کشویی (عملیات، ارتباطات، مشتریان، من، مدیریت) با برچسب پررنگ برای هر دسته، بهجای یک ردیف شلوغ از لینکها
-
راهنمای سایت و درباره ما — لینک راهنما و متن دربارهما از پنل مدیریت پلتفرم قابل تنظیم است (
/superadmin/settings)، صفحه «درباره ما» عمومی در/about -
تنظیمات ربات تلگرام در پنل پلتفرم (توکن + یوزرنیم) — فقط ذخیره میشود؛
⚠️ ارسال واقعی هنوز پیاده نشده چون API تلگرام اجازه پیام به هر آیدی دلخواه را نمیدهد (کاربر باید ابتدا با ربات چت کرده باشد) -
خروجی API برای سازمانهای بیرونی (
/superadmin/external-keys) —⚠️ این یک API فقطخواندنیِ خودمان است (گزارشهای مسروقه/بدهی و زنجیره مالکیت با کلید API)، نه یک اتصال رسمی و تأییدشده به سامانههای واقعی اصناف موبایل ایران، تعزیرات یا مالیات — چون چنین APIای عمومی برای اتصال در دسترس نبود؛ این فقط زیرساخت آماده برای روزی است که آن نهادها بخواهند متصل شوند -
دسترسی پشتیبانی پلتفرم به پنل مغازه — یک کلید روشن/خاموش قابل کنترل هم توسط مدیر مغازه و هم مدیریت پلتفرم (
Shop.supportAccessEnabled)؛⚠️ این فقط یک فلگ رضایت است، هنوز مکانیزم واقعی «ورود بهجای کاربر» (impersonation) پیاده نشده -
موقعیت مکانی مغازه (عرض/طول جغرافیایی) قابل ثبت در پنل مدیریت، با لینک مستقیم به گوگلمپ —
⚠️ نقشهی تعاملی برای انتخاب موقعیت با کلیک نیاز به کلید Google Maps API دارد که در این پروژه تعبیه نشده -
مرجوعی: ویرایش کامل + وضعیت «حلشده/بازگشایی»
-
فراموشی رمز از طریق ایمیل — علاوه بر پیامک، اگر کاربر ایمیل ثبت کرده باشد میتواند کد را به ایمیل دریافت کند؛ ارسال واقعی ایمیل با تنظیمات SMTP از پنل مدیریت پلتفرم (
/superadmin/settings) فعال میشود (مثلاً با Gmail App Password) -
افزودن مشتری دستی به دفترچه مشتریان
-
در صفحه تأیید پذیرش QR، مشخص میشود کدام درخواست از مشتری جدید است (با تأیید، خودکار در دفترچه مشتریان ذخیره میشود)
-
درصد مالیات قابلتنظیم روی هر مغازه (پیشفرض ۱۰٪) — قابل تغییر در پنل مدیریت، و قابل روشن/خاموشکردن هنگام صدور هر فاکتور
-
فاکتور: ویرایش (اجرت، مالیات، وضعیت پرداخت)، حذف (قطعات مصرفی بهصورت خودکار به انبار برمیگردند)، و چاپ فاکتور با یک صفحهی تمیز و مخصوص چاپ (
/invoices/[id]/print) -
داشبورد جداگانه فروشندگان موبایل (
/dealer) — موجودی خرید/فروش گوشی جدا از تیکتهای تعمیر، با سود هر معامله و ثبت خودکار در زنجیره مالکیت IMEI. مدیر از پنل مدیریت («نوع فعالیت مغازه») یا موقع ثبتنام، مغازه را «تعمیرگاه»، «خرید و فروش»، یا «هر دو» تنظیم میکند؛ لینک 💰 خرید و فروش فقط برای مغازههای Dealer/Both در نوار بالا نمایش داده میشود -
نقشه تعاملی نشان برای انتخاب دقیق موقعیت مغازه — کلیک روی نقشه یا کشیدن پین، مختصات خودکار ذخیره میشود. کلید نقشه از
platform.neshan.orgگرفته و در/superadmin/settingsثبت میشود؛ تا وقتی کلید ثبت نشده، فرم بهجای نقشه یک هشدار نشان میدهد و ورودی عددی دستی همچنان بهعنوان جایگزین کار میکند -
حذف واتساپ/تلگرام — چون هیچوقت ارسال واقعی پیاده نشد، تمام فیلدها و بخشهای مرتبط از پروفایل، تنظیمات کارمند و تنظیمات پلتفرم حذف شدند (فقط ایمیل که واقعاً کار میکند باقی ماند)
-
زیرساخت اتصال به سازمانهای بیرونی، کاملاً بدون نیاز به کدنویسی آینده — از
/superadmin/external-keysبرای هر سازمان یک کلید API میسازید و دقیقاً تعیین میکنید به کدام دستهداده دسترسی دارد (گزارش هشدار دستگاه، زنجیره مالکیت، فهرست مغازهها، وضعیت احراز هویت، امتیازات) — با تیک زدن، نه کد نوشتن. اضافهکردن یک دستهداده کاملاً جدید که هنوز وجود ندارد طبیعتاً به توسعه نیاز دارد، اما دستههای فعلی برای اکثر نیازهای این نوع سازمانها کافی است -
مکانیزم واقعی «ورود بهجای کاربر» — لینک یکبارمصرف و ۱۰دقیقهای که فقط با رضایت مغازه (
supportAccessEnabled) کار میکند؛ هنگام استفاده یک بنر قرمز به کارمند/مدیر هشدار میدهد که نشست توسط پشتیبانی باز شده -
گزارش حقوق و دستمزد تجمیعی — مجموع دستمزد هر تعمیرکار در یک بازه دلخواه (پیشفرض ماه جاری) در پنل مدیریت
-
صفحه عمومی مغازه (
/shop/[شناسه]) — قابلاشتراک با مشتری، شامل نام، آدرس، تماس، امتیاز، و مسیریابی روی نقشه -
یادآوری خودکار تمدید اشتراک — یک Cron Job روزانه (
vercel.json+/api/cron/subscription-reminders) به مغازههایی که تا ۳ روز دیگر اشتراکشان تمام میشود پیامک میفرستد؛ نیاز به تنظیمCRON_SECRETدر Vercel دارد (در.env.exampleتوضیح داده شده) -
رمز گوشی در فرم QR مشتری — دقیقاً مثل فرم پذیرش کارمند، مشتری خودش میتواند نوع رمز (پین/پسورد/الگو) و مقدارش را وارد کند
-
داشبورد کامل مشتری (
/customer) — سیستم لاگین و ثبتنام کاملاً جدا برای مشتریان (مدلPlatformCustomer، مستقل از کارکنان مغازه و مستقل از دفترچه مشتریان هر مغازه):- مغازههای اطراف — فهرست تعمیرگاههای فعال با فیلتر استان/شهر، جستجوی نام، نشان سطح احراز هویت، و دکمه «📍 نزدیکترینها» (مرتبسازی بر اساس فاصله واقعی از موقعیت مکانی مرورگر)
- مقایسه امتیازها — میانگین امتیاز و تعداد نظرات هر مغازه کنار هم، مرتبشده از بهترین
- امتیازدهی مستقیم از پنل — هر مشتری برای هر مغازه فقط یک امتیاز (امتیاز جدید جایگزین قبلی میشود تا نشود میانگین را دستکاری کرد)؛ جدا از امتیازدهی پیامکی بعد از تعمیر که مثل قبل کار میکند
- تعمیرهای من — سابقه تعمیر در همه مغازههای عضو بر اساس شماره موبایل مشتری، با وضعیت لحظهای، هزینه/فاکتور، و لینک امتیازدهی بعد از تحویل
- پروفایل (استان/شهر پیشفرض)، تغییر رمز، و بازیابی رمز با کد پیامکی (
/customer/forgot-password) - ورود از
/customer/login— لینک آن در صفحه ورود کارکنان هم هست
-
آپلود واقعی فایل تصویر — عکس پروفایل و تصویر بنر تبلیغات حالا با دکمه «انتخاب و آپلود» از خود دستگاه آپلود میشوند (
/api/upload): در پروڈاکشن روی Vercel Blob (باBLOB_READ_WRITE_TOKEN)، و در حالت توسعه محلی درpublic/uploads. چسباندن لینک مستقیم همچنان بهعنوان جایگزین کار میکند
🚧 نیاز به تکمیل واقعی (اینها هنوز ساخته نشدهاند):
- ارسال واقعی پیام از طریق واتساپ/تلگرام — واتساپ نیاز به تأیید رسمی WhatsApp Business API از متا دارد؛ تلگرام هم محدودیت دارد (کاربر باید ابتدا خودش با ربات چت کرده باشد). فیلدهای مربوطه قبلاً حذف شدند؛ فقط ایمیل که واقعاً کار میکند باقی است
- اتصال رسمی به سامانههای دولتی (اصناف موبایل ایران، تعزیرات، مالیات) — آنچه ساخته شده فقط API خودمان است (
/superadmin/external-keys)؛ اتصال واقعی نیاز به هماهنگی مستقیم با آن سازمانهاست - نقشه داخل پنل مشتری — فهرست مغازهها فاصله را حساب میکند و لینک مسیریابی گوگلمپ دارد، ولی نمایش مغازهها روی یک نقشه تعاملی داخل خود پنل هنوز ساخته نشده (کلید نشان همانطور که برای انتخاب موقعیت مغازه استفاده میشود، اینجا هم قابل استفاده است)
- گزارش حقوق تجمیعی پیشرفتهتر — گزارش بازهای فعلی هست؛ فیش حقوقی رسمی ماهانه با کسورات/پاداش هنوز نه
⚠️ اگر قبلاً یکبار دیپلوی کردهاید: این آپدیت دیتابیس را باز هم تغییر داده (حساب مشتری سراسریPlatformCustomer، کد بازیابی رمز مشتری، اتصال امتیازها به حساب مشتری). اول وابستگی جدید را نصب و بعد مایگریشن را اجرا کنید:npm install npx prisma migrate dev --name customer_dashboard_and_uploadsبرای فعالشدن آپلود واقعی تصویر در پروڈاکشن، در پنل Vercel → Storage → Blob یک استور بسازید تا
BLOB_READ_WRITE_TOKENبه پروژه اضافه شود (بدون آن، آپلود در حالت لوکال کار میکند ولی روی Vercel خطا میدهد و فقط چسباندن لینک ممکن است). همچنین برای فعالشدن یادآوری خودکار اشتراک، در تنظیمات پروژه Vercel یک متغیر محیطیCRON_SECRET(یک رشته تصادفی دلخواه) اضافه کنید — Vercel خودش این مقدار را بهصورت هدر امن برای هر اجرای Cron ارسال میکند.
یکی از این دو را انتخاب کن و یک پروژه بساز:
- https://neon.tech (رایگان، سریع)
- https://supabase.com (رایگان، امکانات بیشتر)
از پنل، connection string را کپی کن.
فایل .env.example را به .env تغییر نام بده و مقداردهی کن:
DATABASE_URL="..." # از مرحله ۱
NEXTAUTH_SECRET="..." # با دستور: openssl rand -base64 32
NEXTAUTH_URL="http://localhost:3000"
KAVENEGAR_API_KEY="" # فعلاً خالی بگذار، مرحله ۵
npm install
npx prisma migrate dev --name init
npm run seed # یک مغازه نمونه با کاربر 09120000000 / رمز 123456 میسازد
npm run devحالا روی http://localhost:3000 بالا میآید.
npm i -g vercel
vercelبعد در پنل Vercel، تمام متغیرهای .env را در Settings → Environment Variables وارد کن، و NEXTAUTH_URL را به آدرس نهایی سایت تغییر بده.
- در https://panel.kavenegar.com ثبتنام کن
- کلید API را از پنل بردار و در
KAVENEGAR_API_KEYبگذار - بدون این کلید، سیستم پیامک واقعی نمیفرستد ولی همهچیز دیگر طبیعی کار میکند (در لاگ سرور چاپ میشود)
- در https://www.zarinpal.com ثبتنام و مرچنت کد (Merchant ID) بگیر
- آن را در
ZARINPAL_MERCHANT_IDبگذار - بدون این مقدار، سیستم بهصورت خودکار از درگاه sandbox زرینپال استفاده میکند — یعنی میتوانی کل فرآیند پرداخت را بدون پول واقعی تست کنی
دستور npm run seed یک حساب نمونه در جدول PlatformAdmin هم میسازد (شماره 09100000000، رمز 123456). حتماً قبل از انتشار عمومی سایت، این رمز را عوض کن یا از طریق Prisma Studio (npm run prisma:studio) یک حساب جدید با رمز امن بساز و نمونه را حذف کن. با این حساب وارد /superadmin/login شو تا لیست همه مغازهها و درآمد کل را ببینی.
هر کوئری به دیتابیس باید با shopId کاربر لاگینشده فیلتر شود. این الزام در lib/tenant.ts (تابع requireSession) متمرکز شده — هر مسیر API باید این تابع را صدا بزند و shopId را از سشن بگیرد، نه از ورودی کاربر. این یعنی حتی اگر یک مغازه سعی کند با دستکاری درخواست به داده مغازه دیگر دسترسی پیدا کند، امکانپذیر نیست.
اگر بخش جدیدی به API اضافه میکنی، همیشه این الگو را رعایت کن:
const { shopId } = await requireSession();
await db.ticket.findMany({ where: { shopId, ...filters } });استثنا: مسیرهای /api/market عمداً بدون فیلتر shopId روی خواندن هستند — چون بازار سراسری قرار است بین همه مغازهها مشترک باشد. همچنان نیاز به لاگین دارند (requireSession صدا زده میشود)، فقط تفکیک تنانت روی این یک مسیر خاص اعمال نمیشود.
استثنای دوم (پنل مشتری): مسیرهای /api/customer/* هم عمداً بینمغازهای هستند — یک مشتری قرار است همه مغازهها را ببیند و سابقه تعمیر خودش را در هر مغازهای پیدا کند. این مسیرها بهجای requireSession از requireCustomer (در lib/tenant.ts) استفاده میکنند: نشست مشتری هرگز shopId یا نقش ندارد و بنابراین هیچ مسیر مغازهای را نمیتواند صدا بزند (و برعکس، نشست کارمند مغازه از سد requireCustomer رد نمیشود). سابقه تعمیر فقط با شماره موبایلِ داخل توکن نشست مطابقت داده میشود، نه شمارهای که از سمت کلاینت بیاید.
app/
api/ ← مسیرهای بکاند (REST)
(dashboard)/ ← صفحات پنل (نیاز به لاگین)
login/
lib/
db.ts ← اتصال Prisma
auth.ts ← تنظیمات NextAuth
tenant.ts ← لایه جداسازی چندمستأجری
sms.ts ← اتصال به Kavenegar
prisma/
schema.prisma ← مدل کامل دیتابیس
seed.ts ← داده نمونه برای تست