Files
2026-10-07 13:39:29 +03:30

217 lines
9.0 KiB
Python

from django.contrib import admin
from django.core.exceptions import PermissionDenied
from django.db import connection
from . import models, policies, services
@admin.register(models.PaymentAccessLink)
class PaymentAccessLinkAdmin(admin.ModelAdmin):
list_display = ('customer', 'notice', 'receipt', 'created_at', 'expires_at', 'revoked_at', 'view_count')
list_filter = ('revoked_at', 'expires_at')
exclude = ('token_hash',)
readonly_fields = ('customer', 'notice', 'receipt', 'created_at', 'expires_at', 'revoked_at', 'view_count', 'last_used_at', 'submitted_receipts')
actions = ['revoke_links']
def has_add_permission(self, request):
return False
def has_delete_permission(self, request, obj=None):
return False
@admin.action(description='ابطال لینک‌های انتخاب‌شده')
def revoke_links(self, request, queryset):
from django.utils import timezone
for link in queryset.filter(revoked_at__isnull=True):
link.revoked_at = timezone.now()
link.save(update_fields=['revoked_at'])
services.audit(request.user, 'PAYMENT_LINK_REVOKED', link)
from .forms import BankAccountForm
class AuditedConfigurationAdmin(admin.ModelAdmin):
permission = 'manage_payment_approval_policy'
singleton_configuration = False
def has_view_permission(self, request, obj=None):
return policies.has_permission(request.user,self.permission)
def has_add_permission(self, request):
return self.has_view_permission(request) and not (self.singleton_configuration and self.model.objects.exists())
def has_change_permission(self, request, obj=None):
return self.has_view_permission(request,obj)
def has_delete_permission(self, request, obj=None):
return False
def save_model(self, request, obj, form, change):
if not self.has_change_permission(request,obj):
raise PermissionDenied
current = self.model.objects.select_for_update().get(pk=obj.pk) if change else None
before = services.configuration_snapshot(current) if current else {}
super().save_model(request,obj,form,change)
services.audit(request.user,'CONFIG_SAVED',obj,{'fields': form.changed_data,
'before':before,'after':services.configuration_snapshot(obj)})
def save_related(self, request, form, formsets, change):
super().save_related(request,form,formsets,change)
if isinstance(form.instance,models.PaymentApprovalPolicy):
services.audit(request.user,'POLICY_ASSIGNMENTS_CHANGED',form.instance,{'reviewers':list(form.instance.reviewers.values_list('pk',flat=True)), 'seniors':list(form.instance.seniors.values_list('pk',flat=True))})
@admin.register(models.PaymentApprovalPolicy)
class ApprovalPolicyAdmin(AuditedConfigurationAdmin):
list_display = ['name','unit','is_active']
filter_horizontal = ['reviewers','seniors']
def formfield_for_manytomany(self, db_field, request, **kwargs):
from apps.accounts.models import User
if db_field.name in ['reviewers','seniors']:
kwargs['queryset'] = User.objects.filter(account_type='STAFF',is_active=True).exclude(staff_profile__is_active=False)
return super().formfield_for_manytomany(db_field,request,**kwargs)
@admin.register(models.PaymentAccessConfiguration)
class AccessAdmin(AuditedConfigurationAdmin):
permission = 'manage_payment_access'
singleton_configuration = True
@admin.register(models.CompanyBankAccount)
class AccountAdmin(AuditedConfigurationAdmin):
form = BankAccountForm
list_display = ['name','bank','requires_deposit_id','is_active','available_to_staff','available_to_customers']
permission = 'manage_company_bankaccounts'
def has_view_permission(self, request, obj=None):
return policies.can_manage_banks(request.user)
def get_queryset(self, request):
queryset = super().get_queryset(request)
# Django wraps POST change forms in atomic(): lock before token validation.
if request.method == 'POST' and connection.in_atomic_block:
return queryset.select_for_update(of=('self',))
return queryset
@admin.register(models.BankInstitution)
class BankAdmin(AccountAdmin):
form = admin.ModelAdmin.form
list_display = ['name','code','is_active','historical']
class EvidenceAdmin(admin.ModelAdmin):
def has_view_permission(self, request, obj=None):
if self.model in [models.PaymentNotice, models.PaymentReceipt]:
return super().has_view_permission(request, obj)
return policies.has_permission(request.user, 'view_payment_audit')
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
def get_queryset(self, request):
queryset = super().get_queryset(request)
if queryset.model in [models.PaymentNotice,models.PaymentReceipt]:
return policies.visible(queryset,request.user)
return queryset if policies.has_permission(request.user,'view_payment_audit') else queryset.none()
for model in [models.PaymentNotice,models.PaymentReceipt,models.PaymentReceiptRevision,models.ReceiptApprovalCycle,
models.ReceiptApprovalAssignment,models.ReceiptDecision,models.PaymentAuditEvent,
models.PaymentNotification,models.PaymentNotificationAttempt]:
admin.site.register(model,EvidenceAdmin)
class ReceiptAlertRecipientInline(admin.TabularInline):
model = models.PaymentReceiptAlertRecipient
extra = 1
can_delete = False
def has_view_permission(self, request, obj=None):
return policies.has_permission(request.user, 'manage_receipt_alerts')
has_add_permission = has_view_permission
has_change_permission = has_view_permission
@admin.register(models.PaymentReceiptAlertConfiguration)
class ReceiptAlertConfigurationAdmin(AuditedConfigurationAdmin):
from .forms import ReceiptAlertConfigurationForm
form = ReceiptAlertConfigurationForm
permission = 'manage_receipt_alerts'
singleton_configuration = True
list_display = ('__str__', 'bale_enabled', 'sms_enabled', 'notify_revisions', 'updated_at')
readonly_fields = ('updated_at',)
inlines = [ReceiptAlertRecipientInline]
fieldsets = (
('بله — تصویر فیش', {'fields': ('bale_enabled', 'bale_token', 'clear_bale_token', 'bale_chat_id', 'bale_caption')}),
('پیامک مدیران', {'fields': ('sms_enabled', 'sms_template')}),
('فیش اصلاح‌شده', {'fields': ('notify_revisions', 'updated_at')}),
)
def save_related(self, request, form, formsets, change):
super().save_related(request, form, formsets, change)
services.audit(request.user, 'RECEIPT_ALERT_RECIPIENTS_CHANGED', form.instance,
{'recipients': list(form.instance.recipients.values('id', 'name', 'mobile', 'is_active'))})
@admin.register(models.PaymentReceiptAlertRecipient)
class ReceiptAlertRecipientAdmin(AuditedConfigurationAdmin):
permission = 'manage_receipt_alerts'
list_display = ('name', 'mobile', 'is_active')
list_filter = ('is_active',)
search_fields = ('name', 'mobile')
@admin.register(models.PaymentReceiptAlert)
class ReceiptAlertAdmin(EvidenceAdmin):
list_display = ('receipt_number', 'recipient_name', 'recipient_mobile', 'channel', 'status', 'attempts', 'delivery_status')
list_select_related = ('job', 'revision__receipt', 'recipient')
readonly_fields = ('channel', 'status', 'attempts', 'delivery_status', 'last_error', 'sent_at', 'delivery_description')
def has_view_permission(self, request, obj=None):
return policies.has_permission(request.user, 'manage_receipt_alerts') or super().has_view_permission(request, obj)
def get_queryset(self, request):
if policies.has_permission(request.user, 'manage_receipt_alerts'):
return admin.ModelAdmin.get_queryset(self, request)
return super().get_queryset(request)
@admin.display(description='فیش')
def receipt_number(self, obj):
return obj.revision.receipt.number
@admin.display(description='کانال')
def channel(self, obj):
return obj.job.get_channel_display()
@admin.display(description='وضعیت ارسال')
def status(self, obj):
return obj.job.get_status_display()
@admin.display(description='تعداد تلاش')
def attempts(self, obj):
return obj.job.attempts
@admin.display(description='دریافت پیامک')
def delivery_status(self, obj):
return obj.job.get_delivery_status_display() or 'استعلام ثبت نشده'
@admin.display(description='آخرین خطا')
def last_error(self, obj):
return obj.job.last_error
@admin.display(description='زمان ارسال')
def sent_at(self, obj):
return obj.job.sent_at
@admin.display(description='شرح دریافت')
def delivery_description(self, obj):
return obj.job.delivery_description