diff --git a/backend/home/views.py b/backend/home/views.py
index eeb8e40..99817bb 100644
--- a/backend/home/views.py
+++ b/backend/home/views.py
@@ -24,11 +24,20 @@ class HomeView(APIView):
sliders = SliderModel.objects.all()
slider_ser = SliderSerializer(instance=sliders, many=True, context={'request': request})
- main_categories = MainCategoryModel.objects.all()
+ main_categories = MainCategoryModel.objects.filter(show_in_home=True)
main_category_ser = MainCategorySerializer(instance=main_categories, many=True, context={'request': request})
- products_to_show = ProductModel.objects.filter(show=True)
- product_ser = DynamicProductSerializer(instance=products_to_show, many=True, context={'request': request, 'view_type': 'list'})
+ top_seller_products = ProductModel.objects.filter(show_in_top_seller=True)
+ top_seller_products_ser = DynamicProductSerializer(instance=top_seller_products, many=True, context={'request': request, 'view_type': 'list'})
+
+ lot_of_discount_products = ProductModel.objects.filter(show_in_lot_of_discount=True)
+ lot_of_discount_products_ser = DynamicProductSerializer(instance=lot_of_discount_products, many=True, context={'request': request, 'view_type': 'list'})
+
+ most_viewed_products = ProductModel.objects.filter(show_in_most_viewed=True)
+ most_viewed_products_ser = DynamicProductSerializer(instance=most_viewed_products, many=True, context={'request': request, 'view_type': 'list'})
+
+ trends_products = ProductModel.objects.filter(show_in_trends=True)
+ trends_products_ser = DynamicProductSerializer(instance=trends_products, many=True, context={'request': request, 'view_type': 'list'})
home_image = HomeImageModel.objects.all().first()
home_image_ser = HomeImageSerializer(instance=home_image, context={'request': request})
@@ -39,7 +48,11 @@ class HomeView(APIView):
response = {
'sliders': slider_ser.data,
'main_categories': main_category_ser.data,
- 'products': product_ser.data,
+ 'products': [],
+ 'top_seller_products': top_seller_products_ser.data,
+ 'lot_of_discount_products': lot_of_discount_products_ser.data,
+ 'most_viewed_products': most_viewed_products_ser.data,
+ 'trends_products': trends_products_ser.data,
'difreance_section': home_image_ser.data,
'show_case_slider': show_cases_ser.data
}
diff --git a/backend/product/admin.py b/backend/product/admin.py
index 94fab0b..c3684e7 100644
--- a/backend/product/admin.py
+++ b/backend/product/admin.py
@@ -12,6 +12,7 @@ from unfold.widgets import UnfoldAdminColorInputWidget
from unfold.decorators import action, display
from utils.admin import ModelAdmin
from django.shortcuts import redirect, render
+from django.utils.html import format_html
from .permissions import ProductDetailCategoryPermission, ProductAdminPermission, ProductVariantAdminPermission, ProductVariantInlineAdminPermission, InPackItemsAdminPermission, AttributeTypeAdminPermission, AttributeValueAdminPermission
from django import forms
@@ -295,16 +296,16 @@ class ProductModelAdmin(ProductAdminPermission, ModelAdmin, ImportExportModelAdm
inlines = [ProductVariantInLine]
readonly_fields = ('slug', 'created_at')
search_fields = ['name', 'description', ]
- list_filter = ['show', ('category', RelatedDropdownFilter), 'show_in_bot', ('category__parent', RelatedDropdownFilter)]
+ list_filter = [('category', RelatedDropdownFilter), 'show_in_bot', ('category__parent', RelatedDropdownFilter)]
list_filter_submit = True
autocomplete_fields = ['related_products', 'shop', 'category']
# compressed_fields = True
warn_unsaved_form = True
# list_per_page = 2
actions_list = ['redirect_to_learn', 'update_products_price']
- list_display = ['display_image', 'shop__shop_name','display_price', 'view', 'show', 'rating', 'category', 'created_at']
+ list_display = ['display_image', 'shop__shop_name','display_price', 'view', 'rating', 'category', 'created_at' ,'show_in_website', ]
fieldsets = (
- ('فیلد های اصلی', {'fields': ('name', 'description', 'category', 'image', 'related_products', 'show', 'shop', 'show_in_bot', 'bot_banner'), "classes": ["tab"],}),
+ ('فیلد های اصلی', {'fields': ('name', 'description', 'category', 'image', 'related_products','show_in_trends', 'show_in_most_viewed', 'show_in_lot_of_discount', 'show_in_top_seller', 'shop', 'show_in_bot', 'bot_banner'), "classes": ["tab"],}),
('فیلد های سيو', {'fields': ('meta_description', 'meta_keywords', 'meta_rating', 'slug'), "classes": ["tab"],}),
('فیلد های مربوط به کاربر', {'fields': ('rating', 'view',), "classes": ["tab"],}),
# ('فیلد های ایتم های پک', {'fields': ('in_pack_items', ), "classes": ["tab"],})
@@ -324,13 +325,18 @@ class ProductModelAdmin(ProductAdminPermission, ModelAdmin, ImportExportModelAdm
if request.user.is_superuser:
return ['slug', 'created_at']
else:
- return ['show_in_bot', 'bot_banner', 'created_at', 'show', 'meta_description', 'meta_keywords', 'meta_rating', 'rating', 'view', 'slug']
+ return ['show_in_bot', 'bot_banner', 'created_at', 'show_in_top_seller','show_in_trends', 'show_in_most_viewed', 'show_in_lot_of_discount','meta_description', 'meta_keywords', 'meta_rating', 'rating', 'view', 'slug']
def display_price(self, obj):
if obj.variants.all().first():
return obj.variants.all().first().price
display_price.short_description = 'قیمت تومانی'
+ def show_in_website(self, obj):
+ url = f"https://heymlz.com/product/{obj.slug}"
+ return format_html('نمایش', url)
+ show_in_website.short_description = 'نمایش در سایت'
+
@display(description='محصول', header=True)
def display_image(self, instance):
if instance and instance.variants.first() and instance.variants.first().images.first():
@@ -398,15 +404,6 @@ class ProductModelAdmin(ProductAdminPermission, ModelAdmin, ImportExportModelAdm
actions = ['bulk_update_subcategory_action']
- # @display(
- # description=("نمایش در صفحه ی اصلی"),
- # label={
- # True: "danger",
- # False: "success",
- # },
- # )
- # def display_show(self, instance):
- # return instance.show
@admin.register(MainCategoryModel)
diff --git a/backend/product/migrations/0071_maincategorymodel_show_in_home.py b/backend/product/migrations/0071_maincategorymodel_show_in_home.py
new file mode 100644
index 0000000..12a5446
--- /dev/null
+++ b/backend/product/migrations/0071_maincategorymodel_show_in_home.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.1.2 on 2026-02-20 15:09
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('product', '0070_enable_pg_trgm'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='maincategorymodel',
+ name='show_in_home',
+ field=models.BooleanField(default=False, verbose_name='نمایش در خانه'),
+ ),
+ ]
diff --git a/backend/product/migrations/0072_remove_productmodel_product_show_idx_and_more.py b/backend/product/migrations/0072_remove_productmodel_product_show_idx_and_more.py
new file mode 100644
index 0000000..60d91fb
--- /dev/null
+++ b/backend/product/migrations/0072_remove_productmodel_product_show_idx_and_more.py
@@ -0,0 +1,58 @@
+# Generated by Django 5.1.2 on 2026-02-20 15:39
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('account', '0034_add_telegram_chat_id_to_shop'),
+ ('product', '0071_maincategorymodel_show_in_home'),
+ ]
+
+ operations = [
+ migrations.RemoveIndex(
+ model_name='productmodel',
+ name='product_show_idx',
+ ),
+ migrations.RemoveField(
+ model_name='productmodel',
+ name='show',
+ ),
+ migrations.AddField(
+ model_name='productmodel',
+ name='show_in_lot_of_discount',
+ field=models.BooleanField(default=False, verbose_name='نمایش در پر تخفیف ها'),
+ ),
+ migrations.AddField(
+ model_name='productmodel',
+ name='show_in_most_viewed',
+ field=models.BooleanField(default=False, verbose_name='نمایش در پر بازدید ها'),
+ ),
+ migrations.AddField(
+ model_name='productmodel',
+ name='show_in_top_seller',
+ field=models.BooleanField(default=False, verbose_name='نمایش در پر فروش ها'),
+ ),
+ migrations.AddField(
+ model_name='productmodel',
+ name='show_in_trends',
+ field=models.BooleanField(default=False, verbose_name='نمایش در ترند ها'),
+ ),
+ migrations.AddIndex(
+ model_name='productmodel',
+ index=models.Index(fields=['show_in_trends'], name='product_show_in_trends_idx'),
+ ),
+ migrations.AddIndex(
+ model_name='productmodel',
+ index=models.Index(fields=['show_in_most_viewed'], name='product_most_viewed_idx'),
+ ),
+ migrations.AddIndex(
+ model_name='productmodel',
+ index=models.Index(fields=['show_in_lot_of_discount'], name='product_lot_of_discount_idx'),
+ ),
+ migrations.AddIndex(
+ model_name='productmodel',
+ index=models.Index(fields=['show_in_top_seller'], name='product_show_in_top_seller_idx'),
+ ),
+ ]
diff --git a/backend/product/models.py b/backend/product/models.py
index 33fc412..1492992 100644
--- a/backend/product/models.py
+++ b/backend/product/models.py
@@ -58,6 +58,7 @@ class MainCategoryModel(models.Model):
blank=True, null=True, verbose_name='ویدیو')
parent = models.ForeignKey(UnitCategoryModel, on_delete=models.SET_NULL,
related_name='maincategorys', verbose_name='دستهبندی والد', null=True)
+ show_in_home = models.BooleanField(default=False, verbose_name='نمایش در خانه')
class Meta:
verbose_name = "دستهبندی اصلی"
verbose_name_plural = "دستهبندیهااصلی"
@@ -180,7 +181,10 @@ class ProductModel(models.Model):
description = models.TextField(verbose_name='توضیحات')
image = models.ImageField(upload_to='product_main/', null=True, blank=True)
rating = models.PositiveIntegerField(default=0, verbose_name='امتیاز')
- show = models.BooleanField(default=False, verbose_name='نمایش در خانه')
+ show_in_trends = models.BooleanField(default=False, verbose_name='نمایش در ترند ها')
+ show_in_most_viewed = models.BooleanField(default=False, verbose_name='نمایش در پر بازدید ها')
+ show_in_lot_of_discount = models.BooleanField(default=False, verbose_name='نمایش در پر تخفیف ها')
+ show_in_top_seller = models.BooleanField(default=False, verbose_name='نمایش در پر فروش ها')
view = models.IntegerField(default=0, verbose_name='بازدید')
slug = models.SlugField(max_length=255, unique=True, blank=True, null=True, allow_unicode=True,
verbose_name='نام یکتا', help_text="این فیلد را خالی بگذارید")
@@ -223,7 +227,10 @@ class ProductModel(models.Model):
models.Index(fields=['category'], name='product_category_idx'),
models.Index(fields=['name'], name='product_name_idx'),
models.Index(fields=['created_at'], name='product_created_at_idx'),
- models.Index(fields=['show'], name='product_show_idx'),
+ models.Index(fields=['show_in_trends'], name='product_show_in_trends_idx'),
+ models.Index(fields=['show_in_most_viewed'], name='product_most_viewed_idx'),
+ models.Index(fields=['show_in_lot_of_discount'], name='product_lot_of_discount_idx'),
+ models.Index(fields=['show_in_top_seller'], name='product_show_in_top_seller_idx'),
models.Index(fields=['category', 'created_at'],
name='product_category_created_idx'),
models.Index(fields=['category', 'name'],
diff --git a/factor/factor.html b/factor/factor.html
new file mode 100644
index 0000000..e389370
--- /dev/null
+++ b/factor/factor.html
@@ -0,0 +1,393 @@
+
+
+
+
+
+ صورتحساب الکترونیکی - فروشگاه هیملز
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
صورت حساب الکترونیکی
+
+

+
+
فروشگاه هیملز
+
+
+
+
+
فروشگاه
هی ملز
+
+
+
+
+ فرستنده :مقدار
+
+
+ شناسه ملی :مقدار
+
+
+ شماره ثبت :مقدار
+
+
+ شماره مجوز :مقدار
+
+
+ نشانی شرکت :مقدار
+
+
+ کد پستی :مقدار
+
+
+ تلفن و فکس :مقدار
+
+
+
+
+
+
+
+ شماره فاکتور :مقدار
+
+
تاریخ :مقدار
+
+ شماره پیگیری :مقدار
+
+
+
+
+
+
+
+
خریدار
+
+
+
خریدار :مقدار
+
+ شماره ملی :مقدار
+
+
+ شماره تماس :مقدار
+
+
+ کد پستی :مقدار
+
+
نشانی :مقدار
+
+
+
+
+
+
+
+
+
+ | ردیف |
+ شناسه کالا یا خدمات |
+ شرح کالا یا خدمات |
+ تعداد |
+ مبلغ واحد (تومان) |
+ مبلغ کل (تومان) |
+ تخفیف (تومان) |
+ مبلغ کل پس از تخفیف (تومان) |
+ جمع مالیات و عوارض ارزش افزوده (تومان) |
+ جمع کل پس از تخفیف و مالیات و عوارض (تومان) |
+
+
+
+
+
+ | 1 |
+ --- |
+ --- |
+ --- |
+ --- |
+ --- |
+ --- |
+ --- |
+ --- |
+ --- |
+
+
+
+ | جمع کل |
+ --- |
+ --- |
+ --- |
+ --- |
+ --- |
+ --- |
+ --- |
+
+
+
+ |
+ جمع کل پس از کسر تخفیف با احتساب مالیات و عوارض (تومان)
+ |
+ --- |
+
+
+
+
+
+
+ مهر و امضای فروشگاه :مقدار
+
+
+ تاریخ تحویل :مقدار
+
+
+ ساعت تحویل :مقدار
+
+
+ روش های پرداخت :مقدار
+
+
+ مهر و امضای خریدار :مقدار
+
+
+
+
+
+
diff --git a/factor/logo-pattern.png b/factor/logo-pattern.png
new file mode 100644
index 0000000..f9f12fe
Binary files /dev/null and b/factor/logo-pattern.png differ
diff --git a/factor/logo2.png b/factor/logo2.png
new file mode 100644
index 0000000..0a0ef47
Binary files /dev/null and b/factor/logo2.png differ
diff --git a/factor/qr-code.png b/factor/qr-code.png
new file mode 100644
index 0000000..c8cbf60
Binary files /dev/null and b/factor/qr-code.png differ
diff --git a/frontend/components/global/product-detail/ProductsSlider.vue b/frontend/components/global/product-detail/ProductsSlider.vue
index d2593c2..401149f 100644
--- a/frontend/components/global/product-detail/ProductsSlider.vue
+++ b/frontend/components/global/product-detail/ProductsSlider.vue
@@ -27,12 +27,12 @@ const onSwiper = (swiper: SwiperClass) => {
-
+
{{ title }}
-
+
-
+
{{ title }}
@@ -118,7 +118,11 @@ const parallaxStyle = computed(() => {
>
{{ price }}
- {{ priceAfter }}
+ {{ priceAfter }}
diff --git a/frontend/components/home/Categories.vue b/frontend/components/home/Categories.vue
index 68a066d..3d83cec 100644
--- a/frontend/components/home/Categories.vue
+++ b/frontend/components/home/Categories.vue
@@ -16,6 +16,12 @@ const activeIndex = ref(0);
const slideElement = ref
(null);
const { width: slideWidth } = useElementSize(slideElement);
+// computed
+
+const slides = computed(() => {
+ return [...homeData.value!.main_categories, ...homeData.value!.main_categories];
+});
+
// methods
const onSwiper = (swiper: SwiperClass) => {
@@ -101,7 +107,7 @@ const onSlideChange = (swiper: SwiperClass) => {
>
-
+
-
-
+
+ title="محصولات پرتخفیف"
+ :products="homeData!.lot_of_discount_products"
+ />
+
+
+
+
+
diff --git a/frontend/pages/profile/index.vue b/frontend/pages/profile/index.vue
index 80b4b8b..4ac2035 100644
--- a/frontend/pages/profile/index.vue
+++ b/frontend/pages/profile/index.vue
@@ -33,7 +33,7 @@ const personalData = ref
({
first_name: account.value?.first_name ?? "",
last_name: account.value?.last_name ?? "",
phone: account.value?.phone ?? "",
- gender: account.value?.gender ?? undefined,
+ gender: account.value?.gender || undefined,
email: account.value?.email ?? "",
birth_date: account.value?.birth_date ?? "",
});
@@ -64,7 +64,7 @@ const formRules = computed(() => {
required: helpers.withMessage("فیلد شماره تلفن الزامی می باشد", required),
phoneValidator: helpers.withMessage(
"شماره تلفن وارد شده معتبر نمی باشد",
- helpers.regex(/^0?[1-9][0-9]{9}$/)
+ helpers.regex(/^0?[1-9][0-9]{9}$/),
),
},
};
@@ -99,7 +99,7 @@ const updateData = () => {
},
});
},
- }
+ },
);
};
@@ -243,6 +243,7 @@ const handleSubmit = (withValidation: boolean) => {