diff --git a/accounts/views.py b/accounts/views.py index 6a4f23c..beb49ff 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -8,7 +8,9 @@ from django import forms from django.contrib.auth.decorators import login_required from accounts.models import Profile from accounts.forms import CustomerForm +from processes.utils import scope_customers_queryset from common.consts import UserRoles +from common.decorators import allowed_roles # Create your views here. @@ -35,9 +37,11 @@ def dashboard(request): @login_required +@allowed_roles([UserRoles.ADMIN, UserRoles.BROKER, UserRoles.MANAGER, UserRoles.ACCOUNTANT]) def customer_list(request): # Get all profiles that have customer role - customers = Profile.objects.filter(roles__slug=UserRoles.CUSTOMER.value, is_deleted=False).select_related('user') + base = Profile.objects.filter(roles__slug=UserRoles.CUSTOMER.value, is_deleted=False).select_related('user') + customers = scope_customers_queryset(request.user, base) form = CustomerForm() return render(request, "accounts/customer_list.html", { @@ -47,6 +51,8 @@ def customer_list(request): @require_POST +@login_required +@allowed_roles([UserRoles.ADMIN, UserRoles.BROKER, UserRoles.MANAGER, UserRoles.ACCOUNTANT]) def add_customer_ajax(request): """AJAX endpoint for adding customers""" form = CustomerForm(request.POST, request.FILES) @@ -85,6 +91,8 @@ def add_customer_ajax(request): @require_POST +@login_required +@allowed_roles([UserRoles.ADMIN, UserRoles.BROKER, UserRoles.MANAGER, UserRoles.ACCOUNTANT]) def edit_customer_ajax(request, customer_id): customer = get_object_or_404(Profile, id=customer_id) form = CustomerForm(request.POST, request.FILES, instance=customer) @@ -122,6 +130,7 @@ def edit_customer_ajax(request, customer_id): }) @require_GET +@login_required def get_customer_data(request, customer_id): customer = get_object_or_404(Profile, id=customer_id) @@ -162,6 +171,7 @@ def get_customer_data(request, customer_id): }) +@login_required def logout_view(request): """Log out current user and redirect to login page.""" logout(request) diff --git a/certificates/views.py b/certificates/views.py index 8cfb704..ce6748e 100644 --- a/certificates/views.py +++ b/certificates/views.py @@ -12,6 +12,7 @@ from .models import CertificateTemplate, CertificateInstance from common.consts import UserRoles from _helpers.jalali import Gregorian +from processes.utils import get_scoped_instance_or_404 def _to_jalali(date_obj): @@ -46,7 +47,7 @@ def _render_template(template: CertificateTemplate, instance: ProcessInstance): @login_required def certificate_step(request, instance_id, step_id): - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) step = get_object_or_404(instance.process.steps, id=step_id) # Ensure all previous steps are completed and invoice settled prior_steps = instance.process.steps.filter(order__lt=instance.current_step.order if instance.current_step else 9999) @@ -128,7 +129,7 @@ def certificate_step(request, instance_id, step_id): @login_required def certificate_print(request, instance_id): - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) cert = CertificateInstance.objects.filter(process_instance=instance).order_by('-created').first() template = cert.template if cert else None return render(request, 'certificates/print.html', { diff --git a/common/decorators.py b/common/decorators.py index 7ac7259..7493abe 100644 --- a/common/decorators.py +++ b/common/decorators.py @@ -3,7 +3,7 @@ from functools import wraps from django.http import JsonResponse, HttpResponse from django.shortcuts import redirect -from extensions.consts import UserRoles +from common.consts import UserRoles def require_ajax(view_func): diff --git a/contracts/views.py b/contracts/views.py index 7a1788b..87c8ff2 100644 --- a/contracts/views.py +++ b/contracts/views.py @@ -11,6 +11,7 @@ from .models import ContractTemplate, ContractInstance from invoices.models import Invoice, Quote from _helpers.utils import jalali_converter2 from django.http import JsonResponse +from processes.utils import get_scoped_instance_or_404 def build_contract_context(instance: ProcessInstance) -> dict: @@ -52,7 +53,7 @@ def build_contract_context(instance: ProcessInstance) -> dict: @login_required def contract_step(request, instance_id, step_id): - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) # Resolve step navigation step = get_object_or_404(instance.process.steps, id=step_id) previous_step = instance.process.steps.filter(order__lt=step.order).last() @@ -117,7 +118,7 @@ def contract_step(request, instance_id, step_id): @login_required def contract_print(request, instance_id): - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) contract = get_object_or_404(ContractInstance, process_instance=instance) return render(request, 'contracts/contract_print.html', { 'instance': instance, diff --git a/db.sqlite3 b/db.sqlite3 index 2808c83..425dfa9 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/installations/views.py b/installations/views.py index ac27db9..008135b 100644 --- a/installations/views.py +++ b/installations/views.py @@ -10,10 +10,11 @@ from accounts.models import Role from invoices.models import Item, Quote, QuoteItem from .models import InstallationAssignment, InstallationReport, InstallationPhoto, InstallationItemChange from decimal import Decimal, InvalidOperation +from processes.utils import get_scoped_instance_or_404 @login_required def installation_assign_step(request, instance_id, step_id): - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) step = get_object_or_404(instance.process.steps, id=step_id) previous_step = instance.process.steps.filter(order__lt=step.order).last() next_step = instance.process.steps.filter(order__gt=step.order).first() @@ -104,7 +105,7 @@ def create_item_changes_for_report(report, remove_map, add_map, quote_price_map) @login_required def installation_report_step(request, instance_id, step_id): - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) step = get_object_or_404(instance.process.steps, id=step_id) previous_step = instance.process.steps.filter(order__lt=step.order).last() diff --git a/invoices/views.py b/invoices/views.py index 8820598..7e01a51 100644 --- a/invoices/views.py +++ b/invoices/views.py @@ -14,11 +14,15 @@ from accounts.models import Role from common.consts import UserRoles from .models import Item, Quote, QuoteItem, Payment, Invoice, InvoiceItem from installations.models import InstallationReport, InstallationItemChange - +from processes.utils import get_scoped_instance_or_404 @login_required def quote_step(request, instance_id, step_id): """مرحله انتخاب اقلام و ساخت پیشفاکتور""" + # Enforce scoped access to prevent URL tampering + instance = get_scoped_instance_or_404(request, instance_id) + + # Enforce scoped access to prevent URL tampering instance = get_object_or_404( ProcessInstance.objects.select_related('process', 'well', 'requester', 'representative', 'representative__profile'), id=instance_id @@ -68,7 +72,7 @@ def quote_step(request, instance_id, step_id): @login_required def create_quote(request, instance_id, step_id): """ساخت/بروزرسانی پیشفاکتور از اقلام انتخابی""" - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) step = get_object_or_404(instance.process.steps, id=step_id) # enforce permission: only BROKER can create/update quote profile = getattr(request.user, 'profile', None) @@ -219,6 +223,9 @@ def create_quote(request, instance_id, step_id): @login_required def quote_preview_step(request, instance_id, step_id): """مرحله صدور پیشفاکتور - نمایش و تایید فاکتور""" + # Enforce scoped access to prevent URL tampering + instance = get_scoped_instance_or_404(request, instance_id) + instance = get_object_or_404( ProcessInstance.objects.select_related('process', 'well', 'requester', 'representative', 'representative__profile', 'broker', 'broker__company', 'broker__affairs', 'broker__affairs__county', 'broker__affairs__county__city'), id=instance_id @@ -261,7 +268,7 @@ def quote_preview_step(request, instance_id, step_id): @login_required def quote_print(request, instance_id): """صفحه پرینت پیشفاکتور""" - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) quote = get_object_or_404(Quote, process_instance=instance) return render(request, 'invoices/quote_print.html', { @@ -274,7 +281,7 @@ def quote_print(request, instance_id): @login_required def approve_quote(request, instance_id, step_id): """تایید پیشفاکتور و انتقال به مرحله بعدی""" - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) step = get_object_or_404(instance.process.steps, id=step_id) quote = get_object_or_404(Quote, process_instance=instance) # enforce permission: only BROKER can approve @@ -316,6 +323,9 @@ def approve_quote(request, instance_id, step_id): @login_required def quote_payment_step(request, instance_id, step_id): """مرحله سوم: ثبت فیشهای واریزی پیشفاکتور""" + # Enforce scoped access to prevent URL tampering + instance = get_scoped_instance_or_404(request, instance_id) + instance = get_object_or_404( ProcessInstance.objects.select_related('process', 'well', 'requester', 'representative', 'representative__profile'), id=instance_id @@ -449,7 +459,7 @@ def quote_payment_step(request, instance_id, step_id): @login_required def add_quote_payment(request, instance_id, step_id): """افزودن فیش واریزی جدید برای پیشفاکتور""" - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) step = get_object_or_404(instance.process.steps, id=step_id) quote = get_object_or_404(Quote, process_instance=instance) invoice, _ = Invoice.objects.get_or_create( @@ -564,7 +574,7 @@ def add_quote_payment(request, instance_id, step_id): @require_POST @login_required def delete_quote_payment(request, instance_id, step_id, payment_id): - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) step = get_object_or_404(instance.process.steps, id=step_id) quote = get_object_or_404(Quote, process_instance=instance) invoice = Invoice.objects.filter(quote=quote).first() @@ -632,6 +642,9 @@ def delete_quote_payment(request, instance_id, step_id, payment_id): @login_required def final_invoice_step(request, instance_id, step_id): """تجمیع اقلام پیشفاکتور با تغییرات نصب و صدور فاکتور نهایی""" + # Enforce scoped access to prevent URL tampering + instance = get_scoped_instance_or_404(request, instance_id) + instance = get_object_or_404( ProcessInstance.objects.select_related('process', 'well', 'requester', 'representative', 'representative__profile'), id=instance_id @@ -770,7 +783,7 @@ def final_invoice_step(request, instance_id, step_id): @login_required def final_invoice_print(request, instance_id): - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) invoice = get_object_or_404(Invoice, process_instance=instance) items = invoice.items.select_related('item').filter(is_deleted=False).all() return render(request, 'invoices/final_invoice_print.html', { @@ -783,7 +796,7 @@ def final_invoice_print(request, instance_id): @require_POST @login_required def approve_final_invoice(request, instance_id, step_id): - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) step = get_object_or_404(instance.process.steps, id=step_id) invoice = get_object_or_404(Invoice, process_instance=instance) # only MANAGER can approve @@ -811,7 +824,7 @@ def approve_final_invoice(request, instance_id, step_id): @login_required def add_special_charge(request, instance_id, step_id): """افزودن هزینه ویژه تعمیر/تعویض به فاکتور نهایی بهصورت آیتم جداگانه""" - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) invoice = get_object_or_404(Invoice, process_instance=instance) # only MANAGER can add special charges try: @@ -848,7 +861,7 @@ def add_special_charge(request, instance_id, step_id): @require_POST @login_required def delete_special_charge(request, instance_id, step_id, item_id): - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) invoice = get_object_or_404(Invoice, process_instance=instance) # only MANAGER can delete special charges try: @@ -870,7 +883,7 @@ def delete_special_charge(request, instance_id, step_id, item_id): @login_required def final_settlement_step(request, instance_id, step_id): - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) step = get_object_or_404(instance.process.steps, id=step_id) if not instance.can_access_step(step): @@ -976,7 +989,7 @@ def final_settlement_step(request, instance_id, step_id): @require_POST @login_required def add_final_payment(request, instance_id, step_id): - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) step = get_object_or_404(instance.process.steps, id=step_id) invoice = get_object_or_404(Invoice, process_instance=instance) # Only BROKER can add final settlement payments @@ -1093,7 +1106,7 @@ def add_final_payment(request, instance_id, step_id): @require_POST @login_required def delete_final_payment(request, instance_id, step_id, payment_id): - instance = get_object_or_404(ProcessInstance, id=instance_id) + instance = get_scoped_instance_or_404(request, instance_id) step = get_object_or_404(instance.process.steps, id=step_id) invoice = get_object_or_404(Invoice, process_instance=instance) payment = get_object_or_404(Payment, id=payment_id, invoice=invoice) diff --git a/processes/templates/processes/request_list.html b/processes/templates/processes/request_list.html index feb1294..611a8de 100644 --- a/processes/templates/processes/request_list.html +++ b/processes/templates/processes/request_list.html @@ -1,5 +1,6 @@ {% extends '_base.html' %} {% load static %} +{% load accounts_tags %} {% block sidebar %} {% include 'sidebars/admin.html' %} @@ -43,10 +44,12 @@ + {% if request.user|is_broker %} + {% endif %} @@ -132,6 +135,91 @@ + {% if access_denied %} +
{{ item.instance.get_status_display_with_color|safe }} | -{{ item.instance.jcreated }} | +{{ item.instance.jcreated_date }} |
|
{% empty %}
||||||||||||||||||
موردی ثبت نشده است | +موردی ثبت نشده است | ++ | + | + | + | + | + | + | + | + |