# ماژول محصول (Product) — ادمین پنل مستند تعریف محصول، تنوع (Variant) و ثبت اطلاعات در ادمین پنل، منطبق بر پیاده‌سازی فعلی. مسیرهای مرتبط: - فرم ثبت/ویرایش: [AddProduct.vue](../apps/admin-panel/src/views/apps/product/AddProduct.vue) (هم برای ایجاد هم ویرایش استفاده می‌شود؛ [EditProduct.vue](../apps/admin-panel/src/views/apps/product/EditProduct.vue) صرفاً یک wrapper است) - لیست محصولات: [ProductList.vue](../apps/admin-panel/src/views/apps/product/ProductList.vue) و مدل جدول [productListModel.ts](../apps/admin-panel/src/views/apps/product/productListModel.ts) - بک‌اند: [admin-products.controller.ts](../apps/api/src/modules/admin-products/admin-products.controller.ts) و [admin-products.service.ts](../apps/api/src/modules/admin-products/admin-products.service.ts) - اسکیمای دیتابیس: [schema.prisma](../apps/api/prisma/schema.prisma) (مدل‌های `Product`, `ProductVariant`, `ProductImage`) --- ## ۱. تعریف محصول (Product) هر محصول یک رکورد پایه دارد که در کارت «اطلاعات پایه» و «تصاویر محصول» فرم پر می‌شود. ### ۱.۱ اطلاعات پایه | فیلد فرم (فارسی) | نام در فرم/پی‌لود | ستون در دیتابیس | نوع | الزامی | توضیح | |---|---|---|---|---|---| | کد محصول | `product_code` | `Product.productCode` | string، یکتا (`@unique`) | خیر | SKU نمایشی؛ در فرم اعتبارسنجی نمی‌شود ولی در دیتابیس یکتا است | | نام محصول | `title` | `Product.title` | string | بله | تنها فیلد با اعتبارسنجی سمت فرم (`err.title`) | | نوع نمایش | `show_type` | `Product.showType` | int، پیش‌فرض `3` | خیر | مقادیر: `1` سایت، `2` گوشی، `3` سایت و گوشی | | دسته‌بندی | `category_id` | `Product.categoryId` (FK به `Category`) | string، اختیاری | خیر | از `GET category` پر می‌شود؛ عنوان‌ها با نوع فروشگاه (`type_str`) پسوند می‌خورند | | برند | `brand_id` | `Product.brandId` (FK به `Brand`) | string، اختیاری | خیر | از `GET brand` | | سینی‌ها (گروه‌بندی) | `ciny_ids` | جدول واسط `CollectionProduct` → مدل `Collection` | آرایه از id | خیر | چند‌انتخابی؛ معادل مدرن فیچر قدیمی «Ciny»، صرفاً یک تگ‌گذاری/گروه‌بندی محصول است، نه چیزی مرتبط با قیمت لحظه‌ای طلا | | شرح محصول (سایت) | `body_html` | `Product.description` | text | خیر | | | نمایش محصول | `is_active` | `Product.isActive` | boolean، پیش‌فرض `true` | خیر | فعال/غیرفعال بودن محصول در فروشگاه | فیلدهای سیستمی که در فرم دیده نمی‌شوند: `id` (cuid)، `createdAt`. > فیلدهای نسخه قدیمی که عمداً حذف شده‌اند (چون ستون معادل در اسکیمای جدید ندارند): `delivery_type`, `message_long_delivery`, `offer_day`, `has_warranty`, `is_exist`, `tags`، و فیلدهای رنگ/اجرت ریالی در تنوع. طبق کامنت بالای فایل فرم، این تصمیم آگاهانه بوده — داده جمع‌آوری و بی‌صدا دور ریخته نمی‌شود. ### ۱.۲ تصاویر محصول محصول به ازای هر تصویر یک رکورد `ProductImage` دارد که با فیلد `role` مشخص می‌شود کدام یک از ۴ اسلات قدیمی است: | بخش فرم (فارسی) | `ProductImageRole` | تعداد | فیلد پی‌لود | |---|---|---|---| | تصویر شاخص | `FEATURED` | تک | `indicator_id` | | تصویر شاخص موبایل | `FEATURED_MOBILE` | تک | `mobile_pic` | | گالری تصاویر سایت | `GALLERY` (پیش‌فرض) | چندتایی | `images` | | گالری تصاویر موبایل | `GALLERY_MOBILE` | چندتایی | `images_mobile` | آپلود از طریق کامپوننت `ImageUploadInput` و اندپوینت عمومی `file/create` انجام می‌شود (همان اندپوینتی که برای آپلود دسته‌بندی/اسلایدر/آواتار هم استفاده می‌شود). فیلد `imageBody` (کپشن هر تصویر گالری در نسخه قدیمی) معادلی در دیتابیس ندارد و حذف شده. --- ## ۲. کارکرد واریانت (تنوع محصول) هر محصول می‌تواند چند «تنوع» (`ProductVariant`) داشته باشد — مثلاً وزن‌های مختلف یک مدل طلا. مدیریت واریانت‌ها **درون همان فرم محصول** و به‌صورت یک جدول این‌لاین با مودال افزودن/ویرایش انجام می‌شود؛ واریانت‌ها تا زمان «ثبت اطلاعات» فقط در حافظه (state) نگه داشته می‌شوند و همراه با کل محصول به سرور ارسال می‌گردند. ### ۲.۱ فیلدهای واریانت | عنوان در مودال | نام فیلد | ستون دیتابیس | نوع | توضیح | |---|---|---|---|---| | عنوان | `title` | `ProductVariant.title` | string، الزامی | تنها فیلد با اعتبارسنجی (`عنوان تنوع الزامی است`) | | وزن | `grams` | `ProductVariant.rashWeightGrams` | Decimal(14,4) | وزن پایه/خام پیش از احتساب اجرت («rash weight») | | رنگ | `color` | `ProductVariant.color` | int، اختیاری | کد رنگ: ۰ سفید، ۱ زرد، ۲ شکلاتی، ۳ رزگلد (برگرفته از `admin/products/colors`) | | اجرت ریالی | `rial_pay` | `ProductVariant.rialWage` | Decimal(18,0) | مبلغ اجرت ریالی ثابت | | اجرت درصدی | `percentage_pay` | `ProductVariant.defaultWagePercent` | Decimal(6,3) | درصد اجرت پیش‌فرض روی وزن پایه | | تعداد | `qty` | `ProductVariant.qty` | int، پیش‌فرض `0` | موجودی انبار | | فعال | `is_active` | `ProductVariant.isActive` | boolean، پیش‌فرض `true` | | | پیش سفارش | `is_pre_order` | `ProductVariant.isPreOrder` | boolean، پیش‌فرض `false` | اگر `true` باشد، حتی با موجودی صفر قابل سفارش است | | توضیح مختصر | `details` | `ProductVariant.details` | text | توضیح کوتاه برای تنوع | | توضیحات کامل | `description` | `ProductVariant.description` | text | توضیحات کامل تنوع | | تصویر اختصاصی | `image_id` | `ProductVariant.imageId` | FK به `ProductImage` | انتخاب عکس اختصاصی تنوع از بین تصاویر گالری محصول | فیلدهای دیگر مدل `ProductVariant` که در فرم نیستند: `sku` (یکتا)، `id`. تمام فیلدهای مرجع henza_admin شامل رنگ، اجرت ریالی و توضیحات تنوع با موفقیت پورت شدند. > قاعده‌ی سبد خرید: اگر `qty = 0` و `isPreOrder = false` باشد، واریانت قابل افزودن به سبد نیست (منطق در `apps/admin-panel/src/stores/cart.ts`). ### ۲.۲ رفتار UI جدول تنوع‌ها - ستون‌ها: عنوان، وزن، اجرت درصدی، تعداد، فعال (تگ سبز/قرمز)، پیش سفارش (تگ آبی/خالی)، عملیات. - **افزودن تنوع**: دکمه بالای جدول → باز کردن مودال خالی با کلید موقت `__key` (برای ردیابی ردیف پیش از ذخیره در سرور). - **ویرایش تنوع**: آیکون ویرایش هر ردیف → پر کردن مودال با مقادیر همان ردیف. - **حذف تنوع**: آیکون حذف → صرفاً از آرایه‌ی حافظه حذف می‌شود؛ حذف واقعی از دیتابیس زمانی رخ می‌دهد که فرم اصلی «ثبت اطلاعات» شود. - کل بخش تنوع‌ها پشت پرمیشن است (زیر بخش «ثبت اطلاعات» دقیق‌تر شرح داده شده). --- ## ۳. ثبت اطلاعات ### ۳.۱ فرم ایجاد/ویرایش - یک فرم واحد (`AddProduct.vue`) هم برای ایجاد و هم ویرایش به کار می‌رود؛ تشخیص حالت از روی وجود `route.params.id`. - **اعتبارسنجی سمت کلاینت**: فقط «نام محصول» (`title`) الزامی است؛ در سطح واریانت فقط «عنوان» الزامی است. سایر فیلدها اختیاری‌اند. - **دکمه ارسال**: «ثبت اطلاعات» → `handleSubmit`. ### ۳.۲ اندپوینت‌ها | حالت | متد | مسیر | |---|---|---| | ایجاد محصول جدید | `POST` | `product/create` | | ویرایش محصول موجود | `PUT` | `product/update/:id` | | نمایش یک محصول (پر کردن فرم ویرایش) | `GET` | `product/show/:id` | | لیست برندها | `GET` | `brand` | | لیست دسته‌بندی‌ها | `POST` | `category` | | لیست سینی‌ها/کالکشن‌ها | `GET` | `admin/products/collections` | ### ۳.۳ ساختار پی‌لود ارسالی ```json { "title": "...", "product_code": "...", "show_type": 3, "category_id": "...", "brand_id": "...", "ciny_ids": ["..."], "body_html": "...", "is_active": true, "indicator_id": "url|null", "mobile_pic": "url|null", "cats": ["category_id"], "images": ["url", "..."], "images_mobile": ["url", "..."], "variants": [ { "id": "فقط در ویرایش واریانت موجود", "title": "...", "grams": 0, "percentage_pay": 0, "qty": 0, "is_active": true, "is_pre_order": false } ] } ``` نکات مهم: - `cats` مشتق از `category_id` است (آرایه‌ای تک‌عضوی یا خالی) — یک artifact از انتقال ساختار قدیمی چند-دسته‌ای. - `images` معادل گالری سایت و `images_mobile` معادل گالری موبایل است؛ تصاویر شاخص جدا (`indicator_id`, `mobile_pic`) ارسال می‌شوند. - کل آرایه `variants` هر بار با کل درخواست ارسال می‌شود (نه اندپوینت جداگانه)؛ بک‌اند مسئول تطبیق/ایجاد/به‌روزرسانی ردیف‌ها بر اساس وجود یا نبود `id` است. ### ۳.۴ بازخورد به کاربر - موفق: پیام `اطلاعات با موفقیت به ثبت رسید.` و ریدایرکت به `/apps/product/list`. - خطا: پیام خطای برگشتی از سرور (`e.response.data.message`) یا پیام پیش‌فرض `خطا در ثبت اطلاعات.` - در حین ارسال، کل فرم پشت `a-spin` (لودینگ) قفل می‌شود. ### ۳.۵ پرمیشن‌های مرتبط با ثبت/مدیریت واریانت | کلید پرمیشن | کنترل می‌کند | |---|---| | `product.list.variants` | نمایش کل بخش «تنوع‌ها» در فرم محصول | | `product.list.variants.view` | مشاهده (تعریف‌شده در `permissionConst.ts`؛ در UI فعلی فرم جداگانه چک نشده) | | `product.list.variants.add` | نمایش دکمه «افزودن تنوع» | | `product.list.variants.edit` | نمایش آیکون ویرایش هر ردیف تنوع | | `product.list.variants.remove` | نمایش آیکون حذف هر ردیف تنوع | ثبت خودِ محصول (فرم اصلی) پرمیشن جداگانه‌ای در این فایل چک نمی‌کند — دسترسی به صفحه از طریق روتینگ/منو کنترل می‌شود. --- ## ۴. لیست محصولات (خلاصه، برای زمینه) جدول لیست (`admin/products/list`) این ستون‌ها را نشان می‌دهد: کد، تصویر محصول، نام محصول، برند، دسته‌بندی، کد محصول، نوع نمایش (با فیلتر انتخابی روی مقادیر ۱/۲/۳)، عملیات. تعریف کامل در [productListModel.ts](../apps/admin-panel/src/views/apps/product/productListModel.ts).