from django.contrib import admin # Register your models here. from .models import * admin.site.register(Customer) admin.site.register(PhoneNumber) admin.site.register(ProductCategory) admin.site.register(Product) admin.site.register(ProductInStore) admin.site.register(Factor) admin.site.register(FactorIteam) admin.site.register(VisitQueue) admin.site.register(ArticleType) admin.site.register(Article) admin.site.register(Sarresid) admin.site.register(Action) admin.site.register(CacheTable) admin.site.register(ProductStockHistory) @admin.register(StoreTransferRequest) class StoreTransferRequestAdmin(admin.ModelAdmin): # ۱. حل مشکل لودینگ و هنگ کردن فرم (تبدیل منوی کشویی غول‌آسا به باکس شناسه عددی) # این دستور فیلدهای پرجمعیت را به شدت بهینه و سبک می‌کند raw_id_fields = ['out_transaction', 'in_transaction', 'product', 'requester', 'approver'] # ۲. ستون‌هایی که می‌خواهید در صفحه جدول (Changelist) نمایش داده شوند list_display = [ 'id', 'source_store', 'target_store', 'product', 'quantity', 'status', 'created_at', 'action_date' ] # ۳. فیکس قطعی باگ N+1 (لود کردن روابط در یک کوئری واحد به جای صدها کوئری تودرتو برای __str__) list_select_related = ['source_store', 'target_store', 'product', 'requester', 'approver'] # ۴. فیلترهای سمت راست صفحه (فقط روی فیلدهای کم‌جمعیت مثل وضعیت و انبار فیلتر بگذارید) list_filter = ['status', 'source_store', 'target_store', 'created_at'] # ۵. باکس جستجوی بهینه شده search_fields = ['id', 'desc', 'product__product_name', 'requester__username'] # ۶. غیرقابل ادیت کردن فیلدهای حساس مالی در پنل ادمین (اختیاری اما برای Enterprise الزامی است) readonly_fields = ['created_at', 'updated_at'] class StoreRoomOwnerInline(admin.TabularInline): """ این کلاس باعث می‌شود مالکین انبار به صورت یک جدول کوچک در داخل همان صفحه مدیریت انبار نمایش داده شوند. """ model = StoreRoomOwner extra = 1 # تعداد ردیف‌های خالی برای افزودن مالک جدید verbose_name = "مالک انبار" verbose_name_plural = "مالکین انبار" @admin.register(StoreRoom) class StoreRoomAdmin(admin.ModelAdmin): list_display = ('store_name', 'get_owners_count') search_fields = ('store_name',) # فیلد user یک M2M معمولی است، پس filter_horizontal روی آن عالی کار می‌کند filter_horizontal = ('user',) # فیلد owners چون جدول واسط دارد، باید از طریق Inline مدیریت شود inlines = [StoreRoomOwnerInline] def get_owners_count(self, obj): """نمایش تعداد مالکین در لیست اصلی انبارها""" return obj.owners.count() get_owners_count.short_description = "تعداد مالکین" # ============================================================ # مدیریت پیامک (SMS) # ============================================================ from django import forms from django.contrib import messages class SMSProviderConfigForm(forms.ModelForm): class Meta: model = SMSProviderConfig fields = '__all__' widgets = { 'password': forms.PasswordInput(render_value=True), 'api_token': forms.PasswordInput(render_value=True), } @admin.register(SMSProviderConfig) class SMSProviderConfigAdmin(admin.ModelAdmin): form = SMSProviderConfigForm list_display = ('provider', 'is_enabled', 'sender', 'username', 'allow_backlog', 'backlog_max', 'max_per_run', 'send_interval_seconds', 'updated_at') readonly_fields = ('updated_at',) fieldsets = ( ("اتصال سرویس‌دهنده", { 'fields': ('provider', 'is_enabled', 'api_url', 'username', 'password', 'sender', 'api_token', 'test_message', 'timeout_seconds'), }), ("وب‌سرویس ۰۰۹۸ (فقط provider وب‌سرویس)", { 'fields': ('webservice_url', 'panel_no'), }), ("کنترل جریان ارسال", { 'fields': ('scan_since', 'allow_backlog', 'backlog_max', 'max_per_run', 'send_interval_seconds', 'updated_at'), }), ) def has_add_permission(self, request): # سینگلتون: فقط ویرایش رکورد موجود. return not SMSProviderConfig.objects.exists() def has_delete_permission(self, request, obj=None): return False @admin.register(SMSTemplate) class SMSTemplateAdmin(admin.ModelAdmin): list_display = ('title', 'sms_type', 'is_enabled', 'auto_send', 'offset_days', 'repeat_interval_days', 'repeat_interval_months') list_editable = ('is_enabled', 'auto_send') list_filter = ('sms_type', 'is_enabled', 'auto_send') search_fields = ('title', 'text') readonly_fields = ('updated_at',) @admin.register(CustomerSMSSetting) class CustomerSMSSettingAdmin(admin.ModelAdmin): list_display = ('customer', 'sms_type', 'is_enabled') list_filter = ('sms_type', 'is_enabled') raw_id_fields = ('customer',) search_fields = ('customer__name_family',) @admin.register(SMSQueue) class SMSQueueAdmin(admin.ModelAdmin): list_display = ('id', 'customer', 'sms_type', 'phone_number', 'status', 'delivery_status', 'attempts', 'created_at', 'sent_at') list_filter = ('status', 'sms_type', 'delivery_status') raw_id_fields = ('customer', 'created_by') search_fields = ('phone_number', 'message', 'dedup_key') readonly_fields = ('created_at', 'updated_at', 'sent_at') actions = ['approve_selected', 'retry_selected'] @admin.action(description="تأیید موارد انتخاب‌شده (ارسال توسط worker)") def approve_selected(self, request, queryset): updated = queryset.filter(status=SMSQueue.Status.PENDING).update( status=SMSQueue.Status.APPROVED, next_retry_at=None, ) self.message_user(request, f"{updated} پیامک تأیید شد.", messages.SUCCESS) @admin.action(description="ارسال مجدد موارد انتخاب‌شده") def retry_selected(self, request, queryset): updated = queryset.update(status=SMSQueue.Status.APPROVED, next_retry_at=None, last_error='') self.message_user(request, f"{updated} پیامک برای ارسال مجدد تأیید شد.", messages.SUCCESS) @admin.register(SMSLog) class SMSLogAdmin(admin.ModelAdmin): list_display = ('id', 'queue', 'attempt_no', 'success', 'provider_code', 'created_at') list_filter = ('success',) raw_id_fields = ('queue',) readonly_fields = ('queue', 'attempt_no', 'success', 'provider_id', 'provider_code', 'error', 'created_at') def has_add_permission(self, request): return False def has_change_permission(self, request, obj=None): return False