Files
tehran_tala/docs/PRODUCT-MODULE.md
2026-09-21 08:32:31 +03:30

156 lines
12 KiB
Markdown

# ماژول محصول (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») |
| اجرت درصدی | `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` باشد، حتی با موجودی صفر قابل سفارش است |
فیلدهای دیگر مدل `ProductVariant` که در فرم نیستند: `sku` (یکتا)، `id`. فیلد رنگ و اجرت ریالی نسخه قدیمی حذف شده‌اند (بدون ستون معادل).
> قاعده‌ی سبد خرید: اگر `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).