delete option staff varizi
This commit is contained in:
1 parent
d99e47ecce
commit
b53a3e0c5c
14 files changed
+383
-3
No files matched your search
@@ -46,6 +46,7 @@ def issue_payment_link(*, notice=None, receipt=None):
|
||||
|
||||
def active_links():
|
||||
return PaymentAccessLink.objects.select_related('customer', 'notice', 'receipt').filter(
|
||||
notice__deleted_at__isnull=True,
|
||||
revoked_at__isnull=True, expires_at__gt=timezone.now(), customer__is_active=True,
|
||||
customer__account_type='CUSTOMER')
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.1 on 2026-10-06 08:47
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('payments', '0004_paymentaccesslink_paymentnotification_access_link_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='paymentnotice',
|
||||
name='deleted_at',
|
||||
field=models.DateTimeField(blank=True, editable=False, null=True),
|
||||
),
|
||||
]
|
||||
@@ -85,6 +85,7 @@ class PaymentNotice(PublicIdModel, TimeStampedModel):
|
||||
invoice_number = models.CharField(max_length=100, blank=True)
|
||||
description = models.TextField(blank=True)
|
||||
cancelled_at = models.DateTimeField(null=True, blank=True)
|
||||
deleted_at = models.DateTimeField(null=True, blank=True, editable=False)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created_at', '-pk']
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from django.db.models import Q
|
||||
from .models import PaymentAccessConfiguration
|
||||
from .models import PaymentAccessConfiguration, PaymentNotice
|
||||
|
||||
|
||||
def is_staff(user):
|
||||
@@ -37,7 +37,29 @@ def can_manage_banks(user):
|
||||
return is_staff(user) and (user.is_superuser or getattr(profile, 'role', '') == 'CEO' or has_permission(user, 'manage_company_bankaccounts'))
|
||||
|
||||
|
||||
def may_delete_notice(user, notice):
|
||||
role = getattr(getattr(user, 'staff_profile', None), 'role', '')
|
||||
return is_staff(user) and (notice.created_by_id == user.pk or user.is_superuser or role in {'CEO', 'GENERAL_ADMIN'})
|
||||
|
||||
|
||||
def can_delete_notice(user, notice):
|
||||
return may_delete_notice(user, notice) and not notice.deleted_at and not notice.receipts.exists()
|
||||
|
||||
|
||||
def notice_actions(queryset, user):
|
||||
"""Allow deletion actors to reach this notice without broadening other lists."""
|
||||
queryset = queryset.filter(deleted_at__isnull=True)
|
||||
if not is_staff(user):
|
||||
return queryset.none()
|
||||
role = getattr(getattr(user, 'staff_profile', None), 'role', '')
|
||||
if user.is_superuser or role in {'CEO', 'GENERAL_ADMIN'}:
|
||||
return queryset
|
||||
return queryset.filter(Q(created_by=user) | Q(pk__in=visible(queryset, user).values('pk')))
|
||||
|
||||
|
||||
def visible(queryset, user):
|
||||
if queryset.model == PaymentNotice:
|
||||
queryset = queryset.filter(deleted_at__isnull=True)
|
||||
if not user.is_authenticated or not user.is_active:
|
||||
return queryset.none()
|
||||
if user.account_type == 'CUSTOMER':
|
||||
|
||||
@@ -27,6 +27,29 @@ def configuration_snapshot(obj):
|
||||
return json.loads(serializers.serialize('json', [obj]))[0]['fields']
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def delete_notice(actor, notice):
|
||||
# Receipt submission locks the same customer, then notice. Do not lock access
|
||||
# links here: anonymous submission already holds its link before the customer.
|
||||
actor = User.objects.select_related('staff_profile').get(pk=actor.pk)
|
||||
User.objects.select_for_update().get(pk=notice.customer_id)
|
||||
notice = PaymentNotice.objects.select_for_update().get(pk=notice.pk)
|
||||
if not policies.may_delete_notice(actor, notice):
|
||||
raise PermissionDenied
|
||||
if notice.receipts.exists():
|
||||
raise ValidationError('برای این اعلامیه فیش ثبت شده است؛ حذف آن امکانپذیر نیست، حتی اگر فیش رد شده باشد.')
|
||||
if notice.deleted_at:
|
||||
return
|
||||
notice.deleted_at = notice.cancelled_at = timezone.now()
|
||||
notice.save(update_fields=['deleted_at', 'cancelled_at', 'updated_at'])
|
||||
from apps.notifications.models import NotificationJob
|
||||
NotificationJob.objects.filter(payment_notification__notice=notice,
|
||||
status__in=['PENDING', 'RETRYING', 'FAILED']).update(status='CANCELLED',
|
||||
last_error='اعلامیه واریز حذف شده است.')
|
||||
audit(actor, 'NOTICE_DELETED', notice, {'number': notice.number, 'customer_id': notice.customer_id,
|
||||
'subject': notice.subject, 'amount': notice.amount})
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def issue_notice(actor, data):
|
||||
unit = TicketPolicy.primary_unit(actor)
|
||||
|
||||
@@ -8,6 +8,7 @@ urlpatterns = [
|
||||
path('notices/generate-identifier/', views.generate_deposit_identifier, name='generate_identifier'),
|
||||
path('notices/new/', views.notice_new, name='notice_new'),
|
||||
path('notices/<uuid:public_id>/', views.notice_detail, name='notice_detail'),
|
||||
path('notices/<uuid:public_id>/delete/', views.notice_delete, name='notice_delete'),
|
||||
path('notices/<uuid:public_id>/payment-link/', views.notice_payment_link, name='notice_payment_link'),
|
||||
path('receipts/new/<uuid:customer_id>/', views.staff_receipt_new, name='receipt_new'),
|
||||
path('receipts/new/', views.staff_receipt_customer, name='receipt_customer'),
|
||||
|
||||
+21
-1
@@ -330,7 +330,7 @@ def notice_new(request):
|
||||
|
||||
@financial_required(staff=True)
|
||||
def notice_detail(request, public_id):
|
||||
notice = complete_notice(get_object_or_404(policies.visible(PaymentNotice.objects.select_related('customer'), request.user), public_id=public_id))
|
||||
notice = complete_notice(get_object_or_404(policies.notice_actions(PaymentNotice.objects.select_related('customer'), request.user), public_id=public_id))
|
||||
services.audit(request.user, 'NOTICE_VIEWED', notice)
|
||||
share = request.session.get('payment_link_share', {})
|
||||
if share.get('notice') == str(public_id):
|
||||
@@ -338,11 +338,31 @@ def notice_detail(request, public_id):
|
||||
else:
|
||||
share = {}
|
||||
return render(request, 'payments/notice_detail.html', staff_context(notice=notice,
|
||||
can_delete_notice=policies.can_delete_notice(request.user, notice),
|
||||
may_delete_notice=policies.may_delete_notice(request.user, notice),
|
||||
can_share_payment_link=policies.can_create(request.user) and notice.customer.is_active,
|
||||
payment_share_url=share.get('url', ''),
|
||||
receipts=policies.visible(notice.receipts.all(), request.user), notifications=notice.paymentnotification_set.select_related('job')))
|
||||
|
||||
|
||||
@financial_required(staff=True)
|
||||
@require_POST
|
||||
def notice_delete(request, public_id):
|
||||
notice = get_object_or_404(policies.notice_actions(PaymentNotice.objects.all(), request.user), public_id=public_id)
|
||||
if not policies.may_delete_notice(request.user, notice):
|
||||
raise PermissionDenied
|
||||
if request.POST.get('confirm') != 'delete':
|
||||
messages.error(request, 'برای حذف اعلامیه، ابتدا هشدار را بخوانید و حذف را تأیید کنید.')
|
||||
return redirect('staff_payments:notice_detail', public_id=public_id)
|
||||
try:
|
||||
services.delete_notice(request.user, notice)
|
||||
except ValidationError as error:
|
||||
messages.error(request, '؛ '.join(error.messages))
|
||||
return redirect('staff_payments:notice_detail', public_id=public_id)
|
||||
messages.success(request, 'اعلامیه واریز حذف شد و لینکهای آن دیگر قابل استفاده نیستند.')
|
||||
return redirect(reverse('staff_payments:desk') + '?tab=notices')
|
||||
|
||||
|
||||
@financial_required(staff=True)
|
||||
@require_POST
|
||||
def notice_payment_link(request, public_id):
|
||||
|
||||
Reference in new issue
Block a user