Files
customerTicket/apps/payments/views.py
T

539 lines
31 KiB
Python

from functools import wraps
from django.contrib import messages
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.paginator import Paginator
from django.db import transaction
from django.db.models import Q, Count, Sum, F, Exists, OuterRef
from django.db.models.deletion import ProtectedError
from django.http import FileResponse, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils import timezone
from django.views.decorators.http import require_POST
from . import policies, services, validators, selectors, customer_picker, access
from .forms import BankAccountForm, NoticeForm, ReceiptForm
from .models import (CompanyBankAccount, PaymentNotice, PaymentReceipt, PaymentReceiptRevision,
PaymentAuditEvent, ReceiptDecision)
def financial_required(staff=False, allow_payment_link=False):
def decorate(view):
@wraps(view)
def wrapped(request, *args, **kwargs):
blocked = access.prepare_payment_request(request, staff=staff, allow_payment_link=allow_payment_link)
if blocked is not None:
return blocked
response = view(request, *args, **kwargs)
if getattr(request, 'payment_access_link', None):
response['Referrer-Policy'] = 'same-origin'
response['X-Robots-Tag'] = 'noindex, nofollow'
return response
return wrapped
return decorate
def staff_context(**kwargs):
return {'active_staff_section': 'payments', 'active_tab_title': 'اعلامیه‌ها و فیش‌های واریزی', **kwargs}
@financial_required()
def home(request):
return render(request, 'payments/home.html', {'financial_page': True})
def complete_notice(notice):
from .deposit_identifier import normalize_deposit_identifier
notice.deposit_id = normalize_deposit_identifier(notice.deposit_id)
snapshot = notice.account_snapshot
approved = getattr(notice, 'approved_total', None)
if approved is None:
approved = notice.receipts.filter(status='APPROVED', revisions__version=F('current_version'), revisions__destination_mismatch=False).aggregate(total=Sum('revisions__amount'))['total'] or 0
notice.approved_amount = approved
notice.remaining_amount = max(0, notice.amount-approved)
notice.settlement_label = 'تسویه شده' if approved >= notice.amount else ('پرداخت جزئی' if approved else 'در انتظار فیش تأییدشده')
# Complete copy uses immutable data, including fields absent from the compact UI.
from apps.core.templatetags.jalali import jalali_datetime
notice.copy_text = '\n'.join(f'{label}: {value}' for label, value in [
('اعلامیه', notice.number), ('تاریخ صدور', jalali_datetime(notice.created_at)), ('موضوع', notice.subject), ('مبلغ (ریال)', f'{notice.amount:,}'),
('نام حساب', snapshot.get('name')), ('بانک', snapshot.get('bank')), ('صاحب حساب', snapshot.get('holder')),
('شماره حساب', snapshot.get('account_number')), ('شبا', snapshot.get('iban')),
('کارت', snapshot.get('card_number')), ('شناسه واریز', notice.deposit_id),
('فاکتور', notice.invoice_number), ('توضیحات', notice.description)] if value)
if notice.deposit_id:
notice.copy_text += '\nوارد کردن شناسه واریز هنگام پرداخت الزامی و اجباری است؛ آن را در فرم بانکی، موبایل‌بانک یا اینترنت‌بانک وارد کنید.'
return notice
@financial_required()
def notices(request):
items = selectors.notices_with_totals(policies.visible(PaymentNotice.objects.all(), request.user))
query = request.GET.get('q', '').strip()
if query:
items = items.filter(Q(subject__icontains=query) | Q(invoice_number__icontains=validators.digits(query)))
page = Paginator(items, 20).get_page(request.GET.get('page'))
page.object_list = selectors.attach_notice_receipts(page.object_list, policies.visible(PaymentReceipt.objects.all(), request.user))
for notice in page:
complete_notice(notice)
params = request.GET.copy()
params.pop('page', None)
return render(request, 'payments/notices.html', {'page_obj': page, 'query': query, 'query_string':params.urlencode(), 'open_notice': request.GET.get('notice', ''), 'financial_page': True})
@financial_required(allow_payment_link=True)
def customer_notice_detail(request, public_id):
notice = get_object_or_404(selectors.notices_with_totals(
access.visible_payments(request, PaymentNotice.objects.all())), public_id=public_id)
selectors.attach_notice_receipts([notice], access.visible_payments(request, PaymentReceipt.objects.all()))
return render(request, 'payments/customer_notice_detail.html', access.customer_context(request,
notice=complete_notice(notice), open_notice=str(notice.public_id), financial_page=True))
@require_POST
@financial_required(allow_payment_link=True)
def customer_notice_share_link(request, public_id):
notice = get_object_or_404(access.visible_payments(request, PaymentNotice.objects.all()),
public_id=public_id, customer__is_active=True)
url = access.notice_share_url(request, notice)
response = JsonResponse({'url': url})
response['Cache-Control'] = 'private, no-store'
return response
def receipt_form_page(request, customer, staff=False):
link = getattr(request, 'payment_access_link', None)
initial = access.receipt_initial(request, customer)
form = ReceiptForm(request.POST or None, request.FILES or None, customer=customer, initial=initial,
notice_ids=[link.notice_id] if link else None)
if request.method == 'POST' and form.is_valid():
try:
receipt = services.submit_receipt(customer if link else request.user, customer, form.cleaned_data, access_link=link)
messages.success(request, 'فیش ثبت شد و در انتظار بررسی است.')
if request.headers.get('Accept') == 'application/json':
return JsonResponse({'redirect_url': reverse('staff_payments:receipt_detail' if staff else 'payments:receipt_detail', args=[receipt.public_id])})
return redirect('staff_payments:receipt_detail' if staff else 'payments:receipt_detail', public_id=receipt.public_id)
except ValidationError as error:
form.add_error(None, error)
if request.method == 'POST' and request.headers.get('Accept') == 'application/json':
return JsonResponse({'errors': form.errors.get_json_data()}, status=422)
selected = form['notice_id'].value()
available = list(form.fields['notice_id'].queryset) if staff else selectors.recent_pending_notice_choices(form)
notice_data = [selectors.notice_form_data(n, staff=staff) for n in available]
from apps.attachments.models import AttachmentPolicy
image_policy = AttachmentPolicy.objects.filter(attachment_type='IMAGE', is_enabled=True).first()
ctx = {'form': form, 'selected_notice': selected, 'notice_data': notice_data, 'financial_page': True,
'max_bytes': image_policy.max_file_size if image_policy else 0, 'staff_form': staff, 'customer': customer,
'account_requirements': form.account_warnings, 'customer_deposit_id': form.customer_deposit_id}
return render(request, 'payments/receipt_form.html', staff_context(**ctx) if staff else access.customer_context(request, **ctx))
@financial_required(allow_payment_link=True)
def receipt_new(request):
return receipt_form_page(request, request.payment_customer)
@financial_required(allow_payment_link=True)
def receipt_list(request):
items = selectors.receipts_with_current_values(access.visible_payments(request, PaymentReceipt.objects.all()).select_related('notice'))
return render(request, 'payments/receipt_list.html', access.customer_context(request, financial_page=True,
page_obj=Paginator(items, 20).get_page(request.GET.get('page'))))
@financial_required(staff=True)
def staff_receipt_customer(request):
if not policies.can_create(request.user):
raise PermissionDenied
from django import forms
form = forms.Form(request.POST or None, initial=customer_picker.initial_customer(request))
# Reuse the same active-customer selector and display labels as notice issuance.
form.fields['customer'] = NoticeForm().fields['customer']
if request.method == 'POST' and form.is_valid():
return redirect('staff_payments:receipt_new', customer_id=form.cleaned_data['customer'].public_id)
return render(request, 'payments/staff_form.html',staff_context(form=form,heading='ثبت فیش برای مشتری',submit_label='ادامه', **customer_picker.picker_context(request, form)))
@financial_required(staff=True)
def customer_search(request):
if not policies.can_create(request.user):
raise PermissionDenied
response = JsonResponse(customer_picker.search_results(request.user, request.GET.get('q', '')))
response['Cache-Control'] = 'private, no-store'
return response
@financial_required(staff=True)
def staff_receipt_new(request, customer_id):
if not policies.can_create(request.user):
raise PermissionDenied
from apps.accounts.models import User
customer = get_object_or_404(User.objects.select_related('customer_profile'), public_id=customer_id, account_type='CUSTOMER', is_active=True)
return receipt_form_page(request, customer, staff=True)
def receipt_context(request, public_id, staff):
receipt = get_object_or_404(access.visible_payments(request, PaymentReceipt.objects.select_related('customer__customer_profile', 'notice', 'owning_unit')), public_id=public_id)
revision = receipt.revisions.select_related('destination', 'origin_bank', 'cycle').get(version=receipt.current_version)
cycle = revision.cycle
services.audit(request.payment_customer, 'RECEIPT_VIEWED', receipt)
decisions = ReceiptDecision.objects.filter(cycle__revision__receipt=receipt).select_related('user', 'cycle__revision').order_by('created_at', 'pk')
return {'receipt': receipt, 'revision': revision, 'cycle': cycle, 'decisions': decisions,
'payment_link_access': getattr(request, 'payment_access_link', None),
'revision_events': PaymentAuditEvent.objects.filter(object_type='payments.paymentreceipt', object_id=str(receipt.public_id), action__in=['RECEIPT_SUBMITTED', 'RECEIPT_REVISED']).order_by('created_at', 'pk'),
'can_approve': staff and policies.can_decide(request.user, receipt, cycle),
'can_senior': staff and policies.can_decide(request.user, receipt, cycle, senior=True),
'can_assign': staff and not cycle.assignments.exists() and policies.has_permission(request.user, 'manage_payment_approval_policy'),
'staff_detail': staff, 'financial_page': True, 'versions': receipt.revisions.select_related('cycle').order_by('version'),
'duplicate_image': PaymentReceiptRevision.objects.filter(sha256=revision.sha256,
receipt__in=policies.visible(PaymentReceipt.objects.all(),request.user)).exclude(receipt=receipt).exists() if staff else False}
@financial_required(allow_payment_link=True)
def receipt_detail(request, public_id):
return render(request, 'payments/receipt_detail.html', receipt_context(request, public_id, False))
@financial_required(staff=True)
def staff_receipt_detail(request, public_id):
return render(request, 'payments/receipt_detail.html', staff_context(**receipt_context(request, public_id, True)))
@financial_required(staff=None, allow_payment_link=True)
def receipt_image(request, public_id):
try:
version = int(request.GET.get('version', '1'))
except ValueError:
from django.http import Http404
raise Http404
revision = get_object_or_404(PaymentReceiptRevision.objects.select_related('receipt'), receipt__public_id=public_id, version=version)
if not access.visible_payments(request, PaymentReceipt.objects.filter(pk=revision.receipt_id)).exists():
raise PermissionDenied
services.audit(request.payment_customer, 'RECEIPT_FILE_REQUESTED', revision.receipt, {'version': revision.version})
# Authorize EVERY access, including debug: financial media never uses MEDIA_URL.
response = FileResponse(revision.image.open('rb'), content_type=revision.mime_type,
as_attachment=request.GET.get('download') == '1', filename=revision.original_name)
response['Cache-Control'] = 'private, no-store'
response['X-Content-Type-Options'] = 'nosniff'
return response
@financial_required(staff=True)
@require_POST
def decision(request, public_id):
get_object_or_404(policies.visible(PaymentReceipt.objects.all(),request.user),public_id=public_id)
try:
version = int(request.POST.get('version', '0'))
receipt = services.decide_receipt(request.user, public_id, version, request.POST.get('action'), request.POST.get('reason', ''),
rejection_confirmed=request.POST.get('confirm_rejection') == '1')
messages.success(request, 'تصمیم ثبت شد. ' + receipt.get_status_display())
except (ValidationError, ValueError) as error:
messages.error(request, '؛ '.join(error.messages) if isinstance(error, ValidationError) else 'نسخه فیش معتبر نیست.')
return redirect('staff_payments:receipt_detail', public_id=public_id)
@financial_required(staff=True)
@require_POST
def assign_policy(request, public_id):
receipt = get_object_or_404(policies.visible(PaymentReceipt.objects.all(), request.user), public_id=public_id)
try:
services.bind_unassigned_cycle(request.user, receipt)
messages.success(request, 'افراد تأییدکننده تعیین شدند.')
except ValidationError as error:
messages.error(request, '؛ '.join(error.messages))
return redirect('staff_payments:receipt_detail', public_id=public_id)
def filtered(queryset, request):
query = validators.digits(request.GET.get('q', ''))
if query:
condition = Q(customer__username__icontains=query) | Q(customer__mobile__icontains=query) | Q(customer__customer_profile__first_name__icontains=query) | Q(customer__customer_profile__last_name__icontains=query) | Q(customer__customer_profile__province__icontains=query) | Q(customer__customer_profile__city__icontains=query)
# PN/PR numbers are UUID prefixes, not database columns.
prefix = query.upper().removeprefix('PN-').removeprefix('PR-').replace('-', '')
if prefix and len(prefix) <= 32 and all(c in '0123456789ABCDEF' for c in prefix):
uuid_prefix = '-'.join(prefix[start:end] for start, end in [(0, 8), (8, 12), (12, 16), (16, 20), (20, 32)] if len(prefix) > start)
condition |= Q(public_id__istartswith=uuid_prefix)
if queryset.model == PaymentNotice:
condition |= Q(subject__icontains=query) | Q(invoice_number__icontains=query)
else:
condition |= Q(notice__invoice_number__icontains=query) | Q(revisions__tracking_number__icontains=query)
queryset = queryset.filter(condition)
for key, field in [('account', 'account_id' if queryset.model == PaymentNotice else 'revisions__destination_id'), ('unit', 'owning_unit_id'), ('customer', 'customer_id')]:
value = request.GET.get(key, '')
if value.isdecimal():
queryset = queryset.filter(**{field: value})
for key, op in [('from', 'gte'), ('to', 'lte')]:
value = request.GET.get(key, '')
if value:
try:
import datetime
parsed = datetime.date.fromisoformat(value)
queryset = queryset.filter(**{f'created_at__date__{op}': parsed})
except ValueError:
messages.error(request, 'بازه تاریخ معتبر نیست.')
return queryset.distinct()
@financial_required(staff=True)
def desk(request):
tab = request.GET.get('tab', 'receipts')
if tab not in {'receipts', 'notices'}:
tab = 'receipts'
mine = tab == 'receipts' and (request.GET.get('mine') == '1' or ('tab' not in request.GET and 'mine' not in request.GET))
receipts = selectors.receipts_with_current_values(policies.visible(PaymentReceipt.objects.select_related('customer__customer_profile', 'notice__created_by__staff_profile'), request.user))
notice_items = selectors.notices_with_totals(policies.visible(PaymentNotice.objects.select_related('customer__customer_profile', 'created_by__staff_profile', 'account'), request.user))
daily_summary = selectors.daily_payment_summary(receipts, notice_items)
if tab == 'notices':
items = filtered(notice_items, request)
state = request.GET.get('notice_state', '')
if state == 'settled':
items = items.filter(cancelled_at__isnull=True, approved_total__gte=F('amount'))
elif state == 'pending':
items = items.filter(cancelled_at__isnull=True, approved_total__lt=F('amount'))
elif state == 'cancelled':
items = items.filter(cancelled_at__isnull=False)
else:
items = filtered(receipts, request)
status = request.GET.get('status', '')
if status in PaymentReceipt.Status.values:
items = items.filter(status=status)
if request.GET.get('kind') == 'independent':
items = items.filter(notice__isnull=True)
elif request.GET.get('kind') == 'linked':
items = items.filter(notice__isnull=False)
if mine:
my_decision = ReceiptDecision.objects.filter(cycle__revision__receipt_id=OuterRef('pk'),
cycle__revision__version=OuterRef('current_version'), user=request.user)
items = items.filter(status__in=['PENDING','PARTIAL'], revisions__version=F('current_version'),
revisions__cycle__assignments__user=request.user).annotate(
my_decision_exists=Exists(my_decision)).filter(my_decision_exists=False).distinct()
page = Paginator(items.order_by('-created_at','-pk'), 25).get_page(request.GET.get('page'))
if tab == 'notices':
page.object_list = selectors.attach_notice_receipts(page.object_list, policies.visible(PaymentReceipt.objects.all(), request.user))
for notice in page.object_list:
complete_notice(notice)
else:
page.object_list = selectors.receipt_review_summaries(page.object_list)
query = request.GET.copy()
query.pop('page', None)
query['tab'] = tab
if mine:
query['mine'] = '1'
else:
query.pop('mine', None)
return render(request, 'payments/desk.html', staff_context(page_obj=page, tab=tab, mine=mine, statuses=PaymentReceipt.Status.choices,
can_create=policies.can_create(request.user), banks=CompanyBankAccount.objects.all(), query_string=query.urlencode(),
daily_summary=daily_summary))
@financial_required(staff=True)
@require_POST
def generate_deposit_identifier(request):
if not policies.can_create(request.user):
raise PermissionDenied
# Resolve the same active customer as NoticeForm; never trust a posted mobile.
try:
customer = NoticeForm().fields['customer'].clean(request.POST.get('customer'))
mobile = validators.digits(customer.local_mobile)
import re
if not re.fullmatch(r'09[0-9]{9}', mobile):
raise ValidationError('شماره موبایل مشتری معتبر نیست؛ ابتدا اطلاعات مشتری را اصلاح کنید.')
except ValidationError as error:
return JsonResponse({'error': '؛ '.join(error.messages)}, status=400)
from .deposit_identifier import generate_id_algorithm
response = JsonResponse({'identifier': generate_id_algorithm(mobile), 'customer': str(customer.pk)})
response['Cache-Control'] = 'no-store'
return response
@financial_required(staff=True)
def notice_new(request):
if not policies.can_create(request.user):
raise PermissionDenied
form = NoticeForm(request.POST or None, initial=customer_picker.initial_customer(request))
if request.method == 'POST' and form.is_valid():
try:
notice = services.issue_notice(request.user, form.cleaned_data)
messages.success(request, 'اعلامیه صادر شد و پیامک در صف اطلاع‌رسانی قرار گرفت.')
return redirect('staff_payments:notice_detail', public_id=notice.public_id)
except ValidationError as error:
form.add_error(None, error)
return render(request, 'payments/staff_form.html', staff_context(form=form, heading='ایجاد اعلامیه واریز', submit_label='صدور و اطلاع‌رسانی', identifier_accounts=list(CompanyBankAccount.objects.filter(requires_deposit_id=True).values_list('pk',flat=True)), **customer_picker.picker_context(request, form)))
@financial_required(staff=True)
def notice_detail(request, 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):
request.session.pop('payment_link_share', None)
else:
share = {}
from uuid import uuid4
from .notifications import notice_sms_pending
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', ''),
can_resend_notice_sms=policies.can_create(request.user, notice.owning_unit) and notice.customer.is_active and not notice.cancelled_at,
notice_sms_pending=notice_sms_pending(notice), notice_sms_request_key=str(uuid4()),
receipts=policies.visible(notice.receipts.all(), request.user), notifications=selectors.notice_sms_history(notice)))
@financial_required(staff=True)
@require_POST
def notice_resend_sms(request, public_id):
notice = get_object_or_404(policies.notice_actions(PaymentNotice.objects.all(), request.user), public_id=public_id)
try:
services.resend_notice_sms(request.user, notice, request.POST.get('request_key', ''))
messages.success(request, 'درخواست ارسال مجدد پیامک در صف ثبت شد؛ نتیجه ارسال و دریافت در سوابق نمایش داده می‌شود.')
except ValidationError as error:
messages.error(request, '؛ '.join(error.messages))
return redirect('staff_payments:notice_detail', public_id=public_id)
@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):
if not policies.can_create(request.user):
raise PermissionDenied
notice = get_object_or_404(policies.visible(PaymentNotice.objects.all(), request.user),
public_id=public_id, customer__is_active=True)
link, url = access.issue_payment_link(notice=notice)
services.audit(request.user, 'PAYMENT_LINK_CREATED', link)
request.session['payment_link_share'] = {'notice': str(public_id), 'url': url}
return redirect('staff_payments:notice_detail', public_id=public_id)
@financial_required(staff=True)
def counts(request):
count = policies.visible(PaymentReceipt.objects.all(), request.user).filter(status__in=['PENDING', 'PARTIAL']).count()
response = JsonResponse({'pending_receipts': count})
response['Cache-Control'] = 'private, no-store'
return response
@financial_required(staff=True)
def banks(request):
if not policies.can_manage_banks(request.user):
raise PermissionDenied
items = CompanyBankAccount.objects.select_related('bank')
query = request.GET.get('q','').strip()
if query:
items = items.filter(Q(name__icontains=query) | Q(bank__name__icontains=query) | Q(holder__icontains=query))
for field in ['is_active', 'available_to_staff', 'available_to_customers']:
if request.GET.get(field) in ['0','1']:
items = items.filter(**{field: request.GET[field] == '1'})
params = request.GET.copy()
params.pop('page', None)
return render(request, 'payments/banks.html', staff_context(page_obj=Paginator(items,25).get_page(request.GET.get('page')),
query_string=params.urlencode(), bank_flags=[{'key':key,'label':label,'value':request.GET.get(key,'')} for key,label in [('is_active','فعال کلی'),('available_to_staff','پنل کارشناسان'),('available_to_customers','پنل مشتریان')]], active_staff_section='banks', active_tab_title='بانک‌ها'))
@financial_required(staff=True)
def bank_edit(request, public_id=None):
if not policies.can_manage_banks(request.user):
raise PermissionDenied
instance = get_object_or_404(CompanyBankAccount, public_id=public_id) if public_id else None
before = services.configuration_snapshot(instance) if instance else {}
form = BankAccountForm(request.POST or None, instance=instance)
if request.method == 'POST' and form.is_valid():
with transaction.atomic():
if instance:
current = CompanyBankAccount.objects.select_for_update().get(pk=instance.pk)
if form.cleaned_data['revision_token'] != current.updated_at.isoformat():
form.add_error(None, 'حساب هم‌زمان تغییر کرده است؛ صفحه را تازه کنید.')
return render(request,'payments/staff_form.html',staff_context(form=form,bank_account=instance,
heading='ویرایش حساب',submit_label='ذخیره حساب',active_staff_section='banks'))
before = services.configuration_snapshot(current)
account = form.save()
services.audit(request.user, 'BANK_SAVED', account, {'before': before, 'after': services.configuration_snapshot(account)})
return redirect('staff_payments:banks')
return render(request,'payments/staff_form.html',staff_context(form=form, bank_account=instance, heading='ویرایش حساب' if instance else 'ایجاد حساب', submit_label='ذخیره حساب', active_staff_section='banks'))
@financial_required(staff=True)
@require_POST
def bank_bulk(request):
if not policies.can_manage_banks(request.user):
raise PermissionDenied
field = request.POST.get('field')
if field not in ['is_active','available_to_staff','available_to_customers'] or request.POST.get('value') not in ['0','1']:
raise ValidationError('عملیات معتبر نیست.')
ids = [value for value in request.POST.getlist('accounts') if value.isdecimal()]
with transaction.atomic():
for account in CompanyBankAccount.objects.select_for_update().filter(pk__in=ids):
before = getattr(account, field)
setattr(account,field,request.POST['value'] == '1')
account.save(update_fields=[field,'updated_at'])
services.audit(request.user,'BANK_FLAG_CHANGED',account,{'field':field,'before':before,'after':getattr(account,field)})
return redirect('staff_payments:banks')
@financial_required(staff=True)
@require_POST
def bank_delete(request, public_id):
if not policies.can_manage_banks(request.user):
raise PermissionDenied
account = get_object_or_404(CompanyBankAccount,public_id=public_id)
try:
with transaction.atomic():
services.audit(request.user,'BANK_DELETED',account,account.snapshot())
account.delete()
except ProtectedError:
messages.error(request,'این حساب سابقه مالی دارد؛ به جای حذف آن را غیرفعال کنید.')
return redirect('staff_payments:banks')
@financial_required(allow_payment_link=True)
def receipt_revise(request, public_id):
link = getattr(request, 'payment_access_link', None)
receipt=get_object_or_404(access.visible_payments(request, PaymentReceipt.objects.all()),public_id=public_id,customer=request.payment_customer,status='REJECTED')
import jdatetime
current = receipt.revisions.get(version=receipt.current_version)
from .identity import customer_name
paid = jdatetime.date.fromgregorian(date=current.paid_date)
initial={'receipt_mode':'linked' if receipt.notice_id else 'independent','notice_id':receipt.notice_id,
'depositor_name':current.depositor_name or customer_name(request.payment_customer),
'paid_amount':str(current.amount),'destination_account':current.destination_id,'deposit_id':current.deposit_id,
'paid_day':str(paid.day),'paid_month':str(paid.month),'paid_year':str(paid.year),
'origin_bank':current.origin_bank_id,'origin_other':current.origin_other,
'tracking_number':current.tracking_number,'description':current.description}
form=ReceiptForm(request.POST or None,request.FILES or None,customer=request.payment_customer,initial=initial,
notice_ids=[link.notice_id] if link else None)
if request.method=='POST' and form.is_valid():
try:
services.revise_receipt(request.payment_customer,receipt.public_id,int(request.POST.get('version','0')),form.cleaned_data)
messages.success(request,'نسخه اصلاحی ثبت شد و دوباره بررسی می‌شود.')
if request.headers.get('Accept') == 'application/json':
return JsonResponse({'redirect_url': reverse('payments:receipt_detail', args=[receipt.public_id])})
return redirect('payments:receipt_detail',public_id=receipt.public_id)
except (ValidationError,ValueError) as error:
form.add_error(None,error if isinstance(error,ValidationError) else 'نسخه فیش معتبر نیست.')
if request.method == 'POST' and request.headers.get('Accept') == 'application/json':
return JsonResponse({'errors': form.errors.get_json_data()}, status=422)
from apps.attachments.models import AttachmentPolicy
policy=AttachmentPolicy.objects.filter(attachment_type='IMAGE',is_enabled=True).first()
notice_data=[selectors.notice_form_data(n) for n in selectors.recent_pending_notice_choices(form, retain_original=receipt.notice_id)]
return render(request,'payments/receipt_form.html',{'form':form,'notice_data':notice_data,'max_bytes':policy.max_file_size if policy else 0,
'payment_link_access': link,
'account_requirements':form.account_warnings, 'customer_deposit_id':form.customer_deposit_id,
'financial_page':True,'revision_version':receipt.current_version})