add scope to filter data
This commit is contained in:
		
							parent
							
								
									394546dc67
								
							
						
					
					
						commit
						e9dec3292c
					
				
					 13 changed files with 386 additions and 36 deletions
				
			
		| 
						 | 
					@ -8,7 +8,9 @@ from django import forms
 | 
				
			||||||
from django.contrib.auth.decorators import login_required
 | 
					from django.contrib.auth.decorators import login_required
 | 
				
			||||||
from accounts.models import Profile
 | 
					from accounts.models import Profile
 | 
				
			||||||
from accounts.forms import CustomerForm
 | 
					from accounts.forms import CustomerForm
 | 
				
			||||||
 | 
					from processes.utils import scope_customers_queryset
 | 
				
			||||||
from common.consts import UserRoles
 | 
					from common.consts import UserRoles
 | 
				
			||||||
 | 
					from common.decorators import allowed_roles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Create your views here.
 | 
					# Create your views here.
 | 
				
			||||||
| 
						 | 
					@ -35,9 +37,11 @@ def dashboard(request):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
 | 
					@allowed_roles([UserRoles.ADMIN, UserRoles.BROKER, UserRoles.MANAGER, UserRoles.ACCOUNTANT])
 | 
				
			||||||
def customer_list(request):
 | 
					def customer_list(request):
 | 
				
			||||||
    # Get all profiles that have customer role
 | 
					    # 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()
 | 
					    form = CustomerForm()
 | 
				
			||||||
    return render(request, "accounts/customer_list.html", {
 | 
					    return render(request, "accounts/customer_list.html", {
 | 
				
			||||||
| 
						 | 
					@ -47,6 +51,8 @@ def customer_list(request):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@require_POST
 | 
					@require_POST
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					@allowed_roles([UserRoles.ADMIN, UserRoles.BROKER, UserRoles.MANAGER, UserRoles.ACCOUNTANT])
 | 
				
			||||||
def add_customer_ajax(request):
 | 
					def add_customer_ajax(request):
 | 
				
			||||||
    """AJAX endpoint for adding customers"""
 | 
					    """AJAX endpoint for adding customers"""
 | 
				
			||||||
    form = CustomerForm(request.POST, request.FILES)
 | 
					    form = CustomerForm(request.POST, request.FILES)
 | 
				
			||||||
| 
						 | 
					@ -85,6 +91,8 @@ def add_customer_ajax(request):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@require_POST
 | 
					@require_POST
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					@allowed_roles([UserRoles.ADMIN, UserRoles.BROKER, UserRoles.MANAGER, UserRoles.ACCOUNTANT])
 | 
				
			||||||
def edit_customer_ajax(request, customer_id):
 | 
					def edit_customer_ajax(request, customer_id):
 | 
				
			||||||
    customer = get_object_or_404(Profile, id=customer_id)
 | 
					    customer = get_object_or_404(Profile, id=customer_id)
 | 
				
			||||||
    form = CustomerForm(request.POST, request.FILES, instance=customer)
 | 
					    form = CustomerForm(request.POST, request.FILES, instance=customer)
 | 
				
			||||||
| 
						 | 
					@ -122,6 +130,7 @@ def edit_customer_ajax(request, customer_id):
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@require_GET
 | 
					@require_GET
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
def get_customer_data(request, customer_id):
 | 
					def get_customer_data(request, customer_id):
 | 
				
			||||||
    customer = get_object_or_404(Profile, id=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):
 | 
					def logout_view(request):
 | 
				
			||||||
    """Log out current user and redirect to login page."""
 | 
					    """Log out current user and redirect to login page."""
 | 
				
			||||||
    logout(request)
 | 
					    logout(request)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@ from .models import CertificateTemplate, CertificateInstance
 | 
				
			||||||
from common.consts import UserRoles
 | 
					from common.consts import UserRoles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from _helpers.jalali import Gregorian
 | 
					from _helpers.jalali import Gregorian
 | 
				
			||||||
 | 
					from processes.utils import get_scoped_instance_or_404
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _to_jalali(date_obj):
 | 
					def _to_jalali(date_obj):
 | 
				
			||||||
| 
						 | 
					@ -46,7 +47,7 @@ def _render_template(template: CertificateTemplate, instance: ProcessInstance):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def certificate_step(request, instance_id, step_id):
 | 
					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)
 | 
					    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
				
			||||||
    # Ensure all previous steps are completed and invoice settled
 | 
					    # 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)
 | 
					    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
 | 
					@login_required
 | 
				
			||||||
def certificate_print(request, instance_id):
 | 
					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()
 | 
					    cert = CertificateInstance.objects.filter(process_instance=instance).order_by('-created').first()
 | 
				
			||||||
    template = cert.template if cert else None
 | 
					    template = cert.template if cert else None
 | 
				
			||||||
    return render(request, 'certificates/print.html', {
 | 
					    return render(request, 'certificates/print.html', {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@ from functools import wraps
 | 
				
			||||||
from django.http import JsonResponse, HttpResponse
 | 
					from django.http import JsonResponse, HttpResponse
 | 
				
			||||||
from django.shortcuts import redirect
 | 
					from django.shortcuts import redirect
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from extensions.consts import UserRoles
 | 
					from common.consts import UserRoles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def require_ajax(view_func):
 | 
					def require_ajax(view_func):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ from .models import ContractTemplate, ContractInstance
 | 
				
			||||||
from invoices.models import Invoice, Quote
 | 
					from invoices.models import Invoice, Quote
 | 
				
			||||||
from _helpers.utils import jalali_converter2
 | 
					from _helpers.utils import jalali_converter2
 | 
				
			||||||
from django.http import JsonResponse
 | 
					from django.http import JsonResponse
 | 
				
			||||||
 | 
					from processes.utils import get_scoped_instance_or_404
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def build_contract_context(instance: ProcessInstance) -> dict:
 | 
					def build_contract_context(instance: ProcessInstance) -> dict:
 | 
				
			||||||
| 
						 | 
					@ -52,7 +53,7 @@ def build_contract_context(instance: ProcessInstance) -> dict:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def contract_step(request, instance_id, step_id):
 | 
					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
 | 
					    # Resolve step navigation
 | 
				
			||||||
    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
					    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
				
			||||||
    previous_step = instance.process.steps.filter(order__lt=step.order).last()
 | 
					    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
 | 
					@login_required
 | 
				
			||||||
def contract_print(request, instance_id):
 | 
					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)
 | 
					    contract = get_object_or_404(ContractInstance, process_instance=instance)
 | 
				
			||||||
    return render(request, 'contracts/contract_print.html', {
 | 
					    return render(request, 'contracts/contract_print.html', {
 | 
				
			||||||
        'instance': instance,
 | 
					        'instance': instance,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								db.sqlite3
									
										
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								db.sqlite3
									
										
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| 
						 | 
					@ -10,10 +10,11 @@ from accounts.models import Role
 | 
				
			||||||
from invoices.models import Item, Quote, QuoteItem
 | 
					from invoices.models import Item, Quote, QuoteItem
 | 
				
			||||||
from .models import InstallationAssignment, InstallationReport, InstallationPhoto, InstallationItemChange
 | 
					from .models import InstallationAssignment, InstallationReport, InstallationPhoto, InstallationItemChange
 | 
				
			||||||
from decimal import Decimal, InvalidOperation
 | 
					from decimal import Decimal, InvalidOperation
 | 
				
			||||||
 | 
					from processes.utils import get_scoped_instance_or_404
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def installation_assign_step(request, instance_id, step_id):
 | 
					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)
 | 
					    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
				
			||||||
    previous_step = instance.process.steps.filter(order__lt=step.order).last()
 | 
					    previous_step = instance.process.steps.filter(order__lt=step.order).last()
 | 
				
			||||||
    next_step = instance.process.steps.filter(order__gt=step.order).first()
 | 
					    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
 | 
					@login_required
 | 
				
			||||||
def installation_report_step(request, instance_id, step_id):
 | 
					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)
 | 
					    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    previous_step = instance.process.steps.filter(order__lt=step.order).last()
 | 
					    previous_step = instance.process.steps.filter(order__lt=step.order).last()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,11 +14,15 @@ from accounts.models import Role
 | 
				
			||||||
from common.consts import UserRoles
 | 
					from common.consts import UserRoles
 | 
				
			||||||
from .models import Item, Quote, QuoteItem, Payment, Invoice, InvoiceItem
 | 
					from .models import Item, Quote, QuoteItem, Payment, Invoice, InvoiceItem
 | 
				
			||||||
from installations.models import InstallationReport, InstallationItemChange
 | 
					from installations.models import InstallationReport, InstallationItemChange
 | 
				
			||||||
 | 
					from processes.utils import get_scoped_instance_or_404
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def quote_step(request, instance_id, step_id):
 | 
					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(
 | 
					    instance = get_object_or_404(
 | 
				
			||||||
        ProcessInstance.objects.select_related('process', 'well', 'requester', 'representative', 'representative__profile'),
 | 
					        ProcessInstance.objects.select_related('process', 'well', 'requester', 'representative', 'representative__profile'),
 | 
				
			||||||
        id=instance_id
 | 
					        id=instance_id
 | 
				
			||||||
| 
						 | 
					@ -68,7 +72,7 @@ def quote_step(request, instance_id, step_id):
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def create_quote(request, instance_id, step_id):
 | 
					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)
 | 
					    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
				
			||||||
    # enforce permission: only BROKER can create/update quote
 | 
					    # enforce permission: only BROKER can create/update quote
 | 
				
			||||||
    profile = getattr(request.user, 'profile', None)
 | 
					    profile = getattr(request.user, 'profile', None)
 | 
				
			||||||
| 
						 | 
					@ -219,6 +223,9 @@ def create_quote(request, instance_id, step_id):
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def quote_preview_step(request, instance_id, step_id):
 | 
					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(
 | 
					    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'),
 | 
					        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
 | 
					        id=instance_id
 | 
				
			||||||
| 
						 | 
					@ -261,7 +268,7 @@ def quote_preview_step(request, instance_id, step_id):
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def quote_print(request, instance_id):
 | 
					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)
 | 
					    quote = get_object_or_404(Quote, process_instance=instance)
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    return render(request, 'invoices/quote_print.html', {
 | 
					    return render(request, 'invoices/quote_print.html', {
 | 
				
			||||||
| 
						 | 
					@ -274,7 +281,7 @@ def quote_print(request, instance_id):
 | 
				
			||||||
@login_required  
 | 
					@login_required  
 | 
				
			||||||
def approve_quote(request, instance_id, step_id):
 | 
					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)
 | 
					    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
				
			||||||
    quote = get_object_or_404(Quote, process_instance=instance)
 | 
					    quote = get_object_or_404(Quote, process_instance=instance)
 | 
				
			||||||
    # enforce permission: only BROKER can approve
 | 
					    # enforce permission: only BROKER can approve
 | 
				
			||||||
| 
						 | 
					@ -316,6 +323,9 @@ def approve_quote(request, instance_id, step_id):
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def quote_payment_step(request, instance_id, step_id):
 | 
					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(
 | 
					    instance = get_object_or_404(
 | 
				
			||||||
        ProcessInstance.objects.select_related('process', 'well', 'requester', 'representative', 'representative__profile'),
 | 
					        ProcessInstance.objects.select_related('process', 'well', 'requester', 'representative', 'representative__profile'),
 | 
				
			||||||
        id=instance_id
 | 
					        id=instance_id
 | 
				
			||||||
| 
						 | 
					@ -449,7 +459,7 @@ def quote_payment_step(request, instance_id, step_id):
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def add_quote_payment(request, instance_id, step_id):
 | 
					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)
 | 
					    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
				
			||||||
    quote = get_object_or_404(Quote, process_instance=instance)
 | 
					    quote = get_object_or_404(Quote, process_instance=instance)
 | 
				
			||||||
    invoice, _ = Invoice.objects.get_or_create(
 | 
					    invoice, _ = Invoice.objects.get_or_create(
 | 
				
			||||||
| 
						 | 
					@ -564,7 +574,7 @@ def add_quote_payment(request, instance_id, step_id):
 | 
				
			||||||
@require_POST
 | 
					@require_POST
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def delete_quote_payment(request, instance_id, step_id, payment_id):
 | 
					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)
 | 
					    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
				
			||||||
    quote = get_object_or_404(Quote, process_instance=instance)
 | 
					    quote = get_object_or_404(Quote, process_instance=instance)
 | 
				
			||||||
    invoice = Invoice.objects.filter(quote=quote).first()
 | 
					    invoice = Invoice.objects.filter(quote=quote).first()
 | 
				
			||||||
| 
						 | 
					@ -632,6 +642,9 @@ def delete_quote_payment(request, instance_id, step_id, payment_id):
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def final_invoice_step(request, instance_id, step_id):
 | 
					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(
 | 
					    instance = get_object_or_404(
 | 
				
			||||||
        ProcessInstance.objects.select_related('process', 'well', 'requester', 'representative', 'representative__profile'),
 | 
					        ProcessInstance.objects.select_related('process', 'well', 'requester', 'representative', 'representative__profile'),
 | 
				
			||||||
        id=instance_id
 | 
					        id=instance_id
 | 
				
			||||||
| 
						 | 
					@ -770,7 +783,7 @@ def final_invoice_step(request, instance_id, step_id):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def final_invoice_print(request, instance_id):
 | 
					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)
 | 
					    invoice = get_object_or_404(Invoice, process_instance=instance)
 | 
				
			||||||
    items = invoice.items.select_related('item').filter(is_deleted=False).all()
 | 
					    items = invoice.items.select_related('item').filter(is_deleted=False).all()
 | 
				
			||||||
    return render(request, 'invoices/final_invoice_print.html', {
 | 
					    return render(request, 'invoices/final_invoice_print.html', {
 | 
				
			||||||
| 
						 | 
					@ -783,7 +796,7 @@ def final_invoice_print(request, instance_id):
 | 
				
			||||||
@require_POST
 | 
					@require_POST
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def approve_final_invoice(request, instance_id, step_id):
 | 
					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)
 | 
					    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
				
			||||||
    invoice = get_object_or_404(Invoice, process_instance=instance)
 | 
					    invoice = get_object_or_404(Invoice, process_instance=instance)
 | 
				
			||||||
    # only MANAGER can approve
 | 
					    # only MANAGER can approve
 | 
				
			||||||
| 
						 | 
					@ -811,7 +824,7 @@ def approve_final_invoice(request, instance_id, step_id):
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def add_special_charge(request, instance_id, step_id):
 | 
					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)
 | 
					    invoice = get_object_or_404(Invoice, process_instance=instance)
 | 
				
			||||||
    # only MANAGER can add special charges
 | 
					    # only MANAGER can add special charges
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
| 
						 | 
					@ -848,7 +861,7 @@ def add_special_charge(request, instance_id, step_id):
 | 
				
			||||||
@require_POST
 | 
					@require_POST
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def delete_special_charge(request, instance_id, step_id, item_id):
 | 
					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)
 | 
					    invoice = get_object_or_404(Invoice, process_instance=instance)
 | 
				
			||||||
    # only MANAGER can delete special charges
 | 
					    # only MANAGER can delete special charges
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
| 
						 | 
					@ -870,7 +883,7 @@ def delete_special_charge(request, instance_id, step_id, item_id):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def final_settlement_step(request, instance_id, step_id):
 | 
					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)
 | 
					    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not instance.can_access_step(step):
 | 
					    if not instance.can_access_step(step):
 | 
				
			||||||
| 
						 | 
					@ -976,7 +989,7 @@ def final_settlement_step(request, instance_id, step_id):
 | 
				
			||||||
@require_POST
 | 
					@require_POST
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def add_final_payment(request, instance_id, step_id):
 | 
					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)
 | 
					    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
				
			||||||
    invoice = get_object_or_404(Invoice, process_instance=instance)
 | 
					    invoice = get_object_or_404(Invoice, process_instance=instance)
 | 
				
			||||||
    # Only BROKER can add final settlement payments
 | 
					    # Only BROKER can add final settlement payments
 | 
				
			||||||
| 
						 | 
					@ -1093,7 +1106,7 @@ def add_final_payment(request, instance_id, step_id):
 | 
				
			||||||
@require_POST
 | 
					@require_POST
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def delete_final_payment(request, instance_id, step_id, payment_id):
 | 
					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)
 | 
					    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
				
			||||||
    invoice = get_object_or_404(Invoice, process_instance=instance)
 | 
					    invoice = get_object_or_404(Invoice, process_instance=instance)
 | 
				
			||||||
    payment = get_object_or_404(Payment, id=payment_id, invoice=invoice)
 | 
					    payment = get_object_or_404(Payment, id=payment_id, invoice=invoice)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
{% extends '_base.html' %}
 | 
					{% extends '_base.html' %}
 | 
				
			||||||
{% load static %}
 | 
					{% load static %}
 | 
				
			||||||
 | 
					{% load accounts_tags %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block sidebar %}
 | 
					{% block sidebar %}
 | 
				
			||||||
    {% include 'sidebars/admin.html' %}
 | 
					    {% include 'sidebars/admin.html' %}
 | 
				
			||||||
| 
						 | 
					@ -43,10 +44,12 @@
 | 
				
			||||||
              </span>
 | 
					              </span>
 | 
				
			||||||
            </span>
 | 
					            </span>
 | 
				
			||||||
          </button>
 | 
					          </button>
 | 
				
			||||||
 | 
					          {% if request.user|is_broker %}
 | 
				
			||||||
          <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#requestModal">
 | 
					          <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#requestModal">
 | 
				
			||||||
            <i class="bx bx-plus me-1"></i>
 | 
					            <i class="bx bx-plus me-1"></i>
 | 
				
			||||||
            درخواست جدید
 | 
					            درخواست جدید
 | 
				
			||||||
          </button>
 | 
					          </button>
 | 
				
			||||||
 | 
					          {% endif %}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
| 
						 | 
					@ -132,6 +135,91 @@
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  {% if access_denied %}
 | 
				
			||||||
 | 
					  <div class="alert alert-warning d-flex align-items-center mb-3" role="alert">
 | 
				
			||||||
 | 
					    <i class="bx bx-info-circle me-2"></i>
 | 
				
			||||||
 | 
					    <div>شما به این بخش دسترسی ندارید.</div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					  {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <div class="card mb-3">
 | 
				
			||||||
 | 
					    <div class="card-body">
 | 
				
			||||||
 | 
					      <form method="get" class="row g-2 align-items-end">
 | 
				
			||||||
 | 
					        <div class="col-sm-6 col-md-3">
 | 
				
			||||||
 | 
					          <label class="form-label">وضعیت درخواست</label>
 | 
				
			||||||
 | 
					          <select class="form-select" name="status">
 | 
				
			||||||
 | 
					            <option value="">همه</option>
 | 
				
			||||||
 | 
					            {% for val, label in status_choices %}
 | 
				
			||||||
 | 
					              <option value="{{ val }}" {% if filter_status == val %}selected{% endif %}>{{ label }}</option>
 | 
				
			||||||
 | 
					            {% endfor %}@require_POST
 | 
				
			||||||
 | 
					            @login_required
 | 
				
			||||||
 | 
					            def delete_request(request, instance_id):
 | 
				
			||||||
 | 
					                """حذف درخواست"""
 | 
				
			||||||
 | 
					                instance = get_object_or_404(ProcessInstance, id=instance_id)
 | 
				
			||||||
 | 
					                # Only BROKER can delete requests and only within their scope
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    profile = getattr(request.user, 'profile', None)
 | 
				
			||||||
 | 
					                    if not (profile and profile.has_role(UserRoles.BROKER)):
 | 
				
			||||||
 | 
					                        return JsonResponse({'success': False, 'message': 'فقط کارگزار مجاز به حذف درخواست است'}, status=403)
 | 
				
			||||||
 | 
					                    # Enforce ownership by broker (prevent deleting others' requests)
 | 
				
			||||||
 | 
					                    if instance.broker_id and profile.broker and instance.broker_id != profile.broker.id:
 | 
				
			||||||
 | 
					                        return JsonResponse({'success': False, 'message': 'شما مجاز به حذف این درخواست نیستید'}, status=403)
 | 
				
			||||||
 | 
					                except Exception:
 | 
				
			||||||
 | 
					                    return JsonResponse({'success': False, 'message': 'فقط کارگزار مجاز به حذف درخواست است'}, status=403)
 | 
				
			||||||
 | 
					                code = instance.code
 | 
				
			||||||
 | 
					                if instance.status == 'completed':
 | 
				
			||||||
 | 
					                    return JsonResponse({
 | 
				
			||||||
 | 
					                        'success': False,
 | 
				
			||||||
 | 
					                        'message': 'درخواست تکمیل شده نمیتواند حذف شود'
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                instance.delete()
 | 
				
			||||||
 | 
					                return JsonResponse({
 | 
				
			||||||
 | 
					                    'success': True,
 | 
				
			||||||
 | 
					                    'message': f'درخواست {code} با موفقیت حذف شد'
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					          </select>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="col-sm-6 col-md-3">
 | 
				
			||||||
 | 
					          <label class="form-label">امور</label>
 | 
				
			||||||
 | 
					          <select class="form-select" name="affairs">
 | 
				
			||||||
 | 
					            <option value="">همه</option>
 | 
				
			||||||
 | 
					            {% for a in affairs_list %}
 | 
				
			||||||
 | 
					              <option value="{{ a.id }}" {% if filter_affairs|default:''|stringformat:'s' == a.id|stringformat:'s' %}selected{% endif %}>{{ a.name }}</option>
 | 
				
			||||||
 | 
					            {% endfor %}
 | 
				
			||||||
 | 
					          </select>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="col-sm-6 col-md-3">
 | 
				
			||||||
 | 
					          <label class="form-label">کارگزار</label>
 | 
				
			||||||
 | 
					          <select class="form-select" name="broker">
 | 
				
			||||||
 | 
					            <option value="">همه</option>
 | 
				
			||||||
 | 
					            {% for b in brokers_list %}
 | 
				
			||||||
 | 
					              <option value="{{ b.id }}" {% if filter_broker|default:''|stringformat:'s' == b.id|stringformat:'s' %}selected{% endif %}>{{ b.name }}</option>
 | 
				
			||||||
 | 
					            {% endfor %}
 | 
				
			||||||
 | 
					          </select>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="col-sm-6 col-md-3">
 | 
				
			||||||
 | 
					          <label class="form-label">مرحله فعلی</label>
 | 
				
			||||||
 | 
					          <select class="form-select" name="step">
 | 
				
			||||||
 | 
					            <option value="">همه</option>
 | 
				
			||||||
 | 
					            {% for s in steps_list %}
 | 
				
			||||||
 | 
					              <option value="{{ s.id }}" {% if filter_step|default:''|stringformat:'s' == s.id|stringformat:'s' %}selected{% endif %}>{{ s.process.name }} - {{ s.name }}</option>
 | 
				
			||||||
 | 
					            {% endfor %}
 | 
				
			||||||
 | 
					          </select>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="col-12 d-flex gap-2 justify-content-end mt-3">
 | 
				
			||||||
 | 
					          <button type="submit" class="btn btn-primary">
 | 
				
			||||||
 | 
					            <i class="bx bx-filter-alt me-1"></i>
 | 
				
			||||||
 | 
					            اعمال فیلتر
 | 
				
			||||||
 | 
					          </button>
 | 
				
			||||||
 | 
					          <a href="?" class="btn btn-outline-secondary">
 | 
				
			||||||
 | 
					            <i class="bx bx-x me-1"></i>
 | 
				
			||||||
 | 
					            حذف فیلتر
 | 
				
			||||||
 | 
					          </a>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </form>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
  <div class="card">
 | 
					  <div class="card">
 | 
				
			||||||
    <div class="card-datatable table-responsive">
 | 
					    <div class="card-datatable table-responsive">
 | 
				
			||||||
      <table id="requests-table" class="datatables-basic table border-top">
 | 
					      <table id="requests-table" class="datatables-basic table border-top">
 | 
				
			||||||
| 
						 | 
					@ -178,7 +266,7 @@
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </td>
 | 
					            </td>
 | 
				
			||||||
            <td>{{ item.instance.get_status_display_with_color|safe }}</td>
 | 
					            <td>{{ item.instance.get_status_display_with_color|safe }}</td>
 | 
				
			||||||
            <td>{{ item.instance.jcreated }}</td>
 | 
					            <td>{{ item.instance.jcreated_date }}</td>
 | 
				
			||||||
            <td>
 | 
					            <td>
 | 
				
			||||||
              <div class="d-inline-block">
 | 
					              <div class="d-inline-block">
 | 
				
			||||||
                <a href="javascript:;" class="btn btn-icon dropdown-toggle hide-arrow" data-bs-toggle="dropdown">
 | 
					                <a href="javascript:;" class="btn btn-icon dropdown-toggle hide-arrow" data-bs-toggle="dropdown">
 | 
				
			||||||
| 
						 | 
					@ -196,19 +284,31 @@
 | 
				
			||||||
                      </a>
 | 
					                      </a>
 | 
				
			||||||
                    {% endif %}
 | 
					                    {% endif %}
 | 
				
			||||||
                  </li>
 | 
					                  </li>
 | 
				
			||||||
 | 
					                  {% if request.user|is_broker %}
 | 
				
			||||||
                  <div class="dropdown-divider"></div>
 | 
					                  <div class="dropdown-divider"></div>
 | 
				
			||||||
                  <li>
 | 
					                  <li>
 | 
				
			||||||
                    <a href="#" class="dropdown-item text-danger" data-instance-id="{{ item.instance.id }}" data-instance-code="{{ item.instance.code }}" onclick="deleteRequest(this.getAttribute('data-instance-id'), this.getAttribute('data-instance-code'))">
 | 
					                    <a href="#" class="dropdown-item text-danger" data-instance-id="{{ item.instance.id }}" data-instance-code="{{ item.instance.code }}" onclick="deleteRequest(this.getAttribute('data-instance-id'), this.getAttribute('data-instance-code'))">
 | 
				
			||||||
                      <i class="bx bx-trash me-1"></i>حذف
 | 
					                      <i class="bx bx-trash me-1"></i>حذف
 | 
				
			||||||
                    </a>
 | 
					                    </a>
 | 
				
			||||||
                  </li>
 | 
					                  </li>
 | 
				
			||||||
 | 
					                  {% endif %}
 | 
				
			||||||
                </ul>
 | 
					                </ul>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </td>
 | 
					            </td>
 | 
				
			||||||
          </tr>
 | 
					          </tr>
 | 
				
			||||||
          {% empty %}
 | 
					          {% empty %}
 | 
				
			||||||
          <tr>
 | 
					          <tr>
 | 
				
			||||||
            <td colspan="11" class="text-center text-muted">موردی ثبت نشده است</td>
 | 
					            <td class="text-center text-muted">موردی ثبت نشده است</td>
 | 
				
			||||||
 | 
					            <td></td>
 | 
				
			||||||
 | 
					            <td></td>
 | 
				
			||||||
 | 
					            <td></td>
 | 
				
			||||||
 | 
					            <td></td>
 | 
				
			||||||
 | 
					            <td></td>
 | 
				
			||||||
 | 
					            <td></td>
 | 
				
			||||||
 | 
					            <td></td>
 | 
				
			||||||
 | 
					            <td></td>
 | 
				
			||||||
 | 
					            <td></td>
 | 
				
			||||||
 | 
					            <td></td>
 | 
				
			||||||
          </tr>
 | 
					          </tr>
 | 
				
			||||||
          {% endfor %}
 | 
					          {% endfor %}
 | 
				
			||||||
        </tbody>
 | 
					        </tbody>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
from django import template
 | 
					from django import template
 | 
				
			||||||
from django.utils.safestring import mark_safe
 | 
					from django.utils.safestring import mark_safe
 | 
				
			||||||
from ..models import ProcessInstance, StepInstance
 | 
					from ..models import ProcessInstance, StepInstance
 | 
				
			||||||
 | 
					from ..utils import count_incomplete_instances
 | 
				
			||||||
 | 
					
 | 
				
			||||||
register = template.Library()
 | 
					register = template.Library()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -104,3 +105,8 @@ def instance_info(instance, modal_id=None):
 | 
				
			||||||
       title="اطلاعات کامل چاه و نماینده"></i>
 | 
					       title="اطلاعات کامل چاه و نماینده"></i>
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    return mark_safe(html)
 | 
					    return mark_safe(html)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@register.simple_tag
 | 
				
			||||||
 | 
					def incomplete_requests_count(user):
 | 
				
			||||||
 | 
					    return count_incomplete_instances(user)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										118
									
								
								processes/utils.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								processes/utils.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,118 @@
 | 
				
			||||||
 | 
					from django.shortcuts import get_object_or_404
 | 
				
			||||||
 | 
					from .models import ProcessInstance
 | 
				
			||||||
 | 
					from common.consts import UserRoles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def scope_instances_queryset(user, queryset=None):
 | 
				
			||||||
 | 
					    """Return a queryset of ProcessInstance scoped by the user's role.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    If no profile/role, returns an empty queryset.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    qs = queryset if queryset is not None else ProcessInstance.objects.all()
 | 
				
			||||||
 | 
					    profile = getattr(user, 'profile', None)
 | 
				
			||||||
 | 
					    if not profile:
 | 
				
			||||||
 | 
					        return qs.none()
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        if profile.has_role(UserRoles.INSTALLER):
 | 
				
			||||||
 | 
					            # Only instances assigned to this installer
 | 
				
			||||||
 | 
					            from installations.models import InstallationAssignment
 | 
				
			||||||
 | 
					            assign_ids = InstallationAssignment.objects.filter(installer=user).values_list('process_instance', flat=True)
 | 
				
			||||||
 | 
					            return qs.filter(id__in=assign_ids)
 | 
				
			||||||
 | 
					        if profile.has_role(UserRoles.BROKER):
 | 
				
			||||||
 | 
					            return qs.filter(broker=profile.broker)
 | 
				
			||||||
 | 
					        if profile.has_role(UserRoles.ACCOUNTANT) or profile.has_role(UserRoles.MANAGER):
 | 
				
			||||||
 | 
					            return qs.filter(broker__affairs__county=profile.county)
 | 
				
			||||||
 | 
					        if profile.has_role(UserRoles.ADMIN):
 | 
				
			||||||
 | 
					            return qs
 | 
				
			||||||
 | 
					        # if profile.has_role(UserRoles.WATER_RESOURCE_MANAGER) or profile.has_role(UserRoles.HEADQUARTER):
 | 
				
			||||||
 | 
					        #     return qs.filter(well__county=profile.county)
 | 
				
			||||||
 | 
					        # Fallback: no special scope
 | 
				
			||||||
 | 
					        # return qs
 | 
				
			||||||
 | 
					    except Exception:
 | 
				
			||||||
 | 
					        return qs.none()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def count_incomplete_instances(user):
 | 
				
			||||||
 | 
					    """Count non-completed, non-deleted requests within the user's scope."""
 | 
				
			||||||
 | 
					    base = ProcessInstance.objects.select_related('well').filter(is_deleted=False).exclude(status='completed')
 | 
				
			||||||
 | 
					    return scope_instances_queryset(user, base).count()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def user_can_access_instance(user, instance: ProcessInstance) -> bool:
 | 
				
			||||||
 | 
					    """Check if user can access a specific instance based on scoping rules."""
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        scoped = scope_instances_queryset(user, ProcessInstance.objects.filter(id=instance.id))
 | 
				
			||||||
 | 
					        return scoped.exists()
 | 
				
			||||||
 | 
					    except Exception:
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_scoped_instance_or_404(request, instance_id: int) -> ProcessInstance:
 | 
				
			||||||
 | 
					    """Return instance only if it's within the user's scope; otherwise 404.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Use this in any view receiving instance_id from URL to prevent URL tampering.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    base = ProcessInstance.objects.filter(is_deleted=False)
 | 
				
			||||||
 | 
					    qs = scope_instances_queryset(request.user, base)
 | 
				
			||||||
 | 
					    return get_object_or_404(qs, id=instance_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def scope_wells_queryset(user, queryset=None):
 | 
				
			||||||
 | 
					    """Return a queryset of Well scoped by the user's role (parity with instances)."""
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        from wells.models import Well
 | 
				
			||||||
 | 
					        qs = queryset if queryset is not None else Well.objects.all()
 | 
				
			||||||
 | 
					        profile = getattr(user, 'profile', None)
 | 
				
			||||||
 | 
					        if not profile:
 | 
				
			||||||
 | 
					            return qs.none()
 | 
				
			||||||
 | 
					        if profile.has_role(UserRoles.ADMIN):
 | 
				
			||||||
 | 
					            return qs
 | 
				
			||||||
 | 
					        if profile.has_role(UserRoles.BROKER):
 | 
				
			||||||
 | 
					            return qs.filter(broker=profile.broker)
 | 
				
			||||||
 | 
					        if profile.has_role(UserRoles.ACCOUNTANT) or profile.has_role(UserRoles.MANAGER):
 | 
				
			||||||
 | 
					            return qs.filter(broker__affairs__county=profile.county)
 | 
				
			||||||
 | 
					        if profile.has_role(UserRoles.INSTALLER):
 | 
				
			||||||
 | 
					            # Wells that have instances assigned to this installer
 | 
				
			||||||
 | 
					            from installations.models import InstallationAssignment
 | 
				
			||||||
 | 
					            assign_ids = InstallationAssignment.objects.filter(installer=user).values_list('process_instance', flat=True)
 | 
				
			||||||
 | 
					            inst_qs = ProcessInstance.objects.filter(id__in=assign_ids)
 | 
				
			||||||
 | 
					            return qs.filter(process_instances__in=inst_qs).distinct()
 | 
				
			||||||
 | 
					        # Fallback
 | 
				
			||||||
 | 
					        return qs.none()
 | 
				
			||||||
 | 
					    except Exception:
 | 
				
			||||||
 | 
					        return qs.none() if 'qs' in locals() else []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def scope_customers_queryset(user, queryset=None):
 | 
				
			||||||
 | 
					    """Return a queryset of customer Profiles scoped by user's role.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Assumes queryset is Profiles already filtered to customers, otherwise we filter here.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        from accounts.models import Profile
 | 
				
			||||||
 | 
					        qs = queryset if queryset is not None else Profile.objects.all()
 | 
				
			||||||
 | 
					        # Ensure we're only looking at customer profiles
 | 
				
			||||||
 | 
					        from common.consts import UserRoles as UR
 | 
				
			||||||
 | 
					        qs = qs.filter(roles__slug=UR.CUSTOMER.value, is_deleted=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        profile = getattr(user, 'profile', None)
 | 
				
			||||||
 | 
					        if not profile:
 | 
				
			||||||
 | 
					            return qs.none()
 | 
				
			||||||
 | 
					        if profile.has_role(UserRoles.ADMIN):
 | 
				
			||||||
 | 
					            return qs
 | 
				
			||||||
 | 
					        if profile.has_role(UserRoles.BROKER):
 | 
				
			||||||
 | 
					            return qs.filter(broker=profile.broker)
 | 
				
			||||||
 | 
					        if profile.has_role(UserRoles.ACCOUNTANT) or profile.has_role(UserRoles.MANAGER):
 | 
				
			||||||
 | 
					            return qs.filter(county=profile.county)
 | 
				
			||||||
 | 
					        if profile.has_role(UserRoles.INSTALLER):
 | 
				
			||||||
 | 
					            # Customers that are representatives of instances assigned to this installer
 | 
				
			||||||
 | 
					            from installations.models import InstallationAssignment
 | 
				
			||||||
 | 
					            assign_ids = InstallationAssignment.objects.filter(installer=user).values_list('process_instance', flat=True)
 | 
				
			||||||
 | 
					            rep_ids = ProcessInstance.objects.filter(id__in=assign_ids).values_list('representative', flat=True)
 | 
				
			||||||
 | 
					            return qs.filter(user_id__in=rep_ids)
 | 
				
			||||||
 | 
					        # Fallback
 | 
				
			||||||
 | 
					        return qs.none()
 | 
				
			||||||
 | 
					    except Exception:
 | 
				
			||||||
 | 
					        return qs.none() if 'qs' in locals() else []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,19 +7,62 @@ from django.http import JsonResponse
 | 
				
			||||||
from django.views.decorators.http import require_POST, require_GET
 | 
					from django.views.decorators.http import require_POST, require_GET
 | 
				
			||||||
from django.db import transaction
 | 
					from django.db import transaction
 | 
				
			||||||
from django.contrib.auth import get_user_model
 | 
					from django.contrib.auth import get_user_model
 | 
				
			||||||
from .models import Process, ProcessInstance, StepInstance
 | 
					from .models import Process, ProcessInstance, StepInstance, ProcessStep
 | 
				
			||||||
 | 
					from .utils import scope_instances_queryset, get_scoped_instance_or_404
 | 
				
			||||||
 | 
					from installations.models import InstallationAssignment
 | 
				
			||||||
from wells.models import Well
 | 
					from wells.models import Well
 | 
				
			||||||
from accounts.models import Profile
 | 
					from accounts.models import Profile, Broker
 | 
				
			||||||
 | 
					from locations.models import Affairs
 | 
				
			||||||
from accounts.forms import CustomerForm
 | 
					from accounts.forms import CustomerForm
 | 
				
			||||||
from wells.forms import WellForm
 | 
					from wells.forms import WellForm
 | 
				
			||||||
from wells.models import WaterMeterManufacturer
 | 
					from wells.models import WaterMeterManufacturer
 | 
				
			||||||
 | 
					from common.consts import UserRoles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def request_list(request):
 | 
					def request_list(request):
 | 
				
			||||||
    """نمایش لیست درخواستها با جدول و مدال ایجاد"""
 | 
					    """نمایش لیست درخواستها با جدول و مدال ایجاد"""
 | 
				
			||||||
    instances = ProcessInstance.objects.select_related('well', 'representative', 'requester').prefetch_related('step_instances__step').filter(is_deleted=False).order_by('-created')
 | 
					    instances = ProcessInstance.objects.select_related('well', 'representative', 'requester', 'broker', 'current_step', 'process').prefetch_related('step_instances__step').filter(is_deleted=False).order_by('-created')
 | 
				
			||||||
 | 
					    access_denied = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # filter by roles (scoped queryset)
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        instances = scope_instances_queryset(request.user, instances)
 | 
				
			||||||
 | 
					        if not instances.exists() and not getattr(request.user, 'profile', None):
 | 
				
			||||||
 | 
					            access_denied = True
 | 
				
			||||||
 | 
					            instances = instances.none()
 | 
				
			||||||
 | 
					    except Exception:
 | 
				
			||||||
 | 
					        access_denied = True
 | 
				
			||||||
 | 
					        instances = instances.none()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    # Filters
 | 
				
			||||||
 | 
					    status_q = (request.GET.get('status') or '').strip()
 | 
				
			||||||
 | 
					    affairs_q = (request.GET.get('affairs') or '').strip()
 | 
				
			||||||
 | 
					    broker_q = (request.GET.get('broker') or '').strip()
 | 
				
			||||||
 | 
					    step_q = (request.GET.get('step') or '').strip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if status_q:
 | 
				
			||||||
 | 
					        instances = instances.filter(status=status_q)
 | 
				
			||||||
 | 
					    if affairs_q:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            instances = instances.filter(well__affairs_id=int(affairs_q))
 | 
				
			||||||
 | 
					        except Exception:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					    if broker_q:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            instances = instances.filter(broker_id=int(broker_q))
 | 
				
			||||||
 | 
					        except Exception:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					    if step_q:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            instances = instances.filter(current_step_id=int(step_q))
 | 
				
			||||||
 | 
					        except Exception:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
    processes = Process.objects.filter(is_active=True)
 | 
					    processes = Process.objects.filter(is_active=True)
 | 
				
			||||||
 | 
					    status_choices = list(ProcessInstance.STATUS_CHOICES)
 | 
				
			||||||
 | 
					    affairs_list = Affairs.objects.all().order_by('name')
 | 
				
			||||||
 | 
					    brokers_list = Broker.objects.all().order_by('name')
 | 
				
			||||||
 | 
					    steps_list = ProcessStep.objects.select_related('process').all().order_by('process__name', 'order')
 | 
				
			||||||
    manufacturers = WaterMeterManufacturer.objects.all().order_by('name')
 | 
					    manufacturers = WaterMeterManufacturer.objects.all().order_by('name')
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    # Calculate progress for each instance
 | 
					    # Calculate progress for each instance
 | 
				
			||||||
| 
						 | 
					@ -52,6 +95,16 @@ def request_list(request):
 | 
				
			||||||
        'completed_count': completed_count,
 | 
					        'completed_count': completed_count,
 | 
				
			||||||
        'in_progress_count': in_progress_count,
 | 
					        'in_progress_count': in_progress_count,
 | 
				
			||||||
        'pending_count': pending_count,
 | 
					        'pending_count': pending_count,
 | 
				
			||||||
 | 
					        # filter context
 | 
				
			||||||
 | 
					        'status_choices': status_choices,
 | 
				
			||||||
 | 
					        'affairs_list': affairs_list,
 | 
				
			||||||
 | 
					        'brokers_list': brokers_list,
 | 
				
			||||||
 | 
					        'steps_list': steps_list,
 | 
				
			||||||
 | 
					        'filter_status': status_q,
 | 
				
			||||||
 | 
					        'filter_affairs': affairs_q,
 | 
				
			||||||
 | 
					        'filter_broker': broker_q,
 | 
				
			||||||
 | 
					        'filter_step': step_q,
 | 
				
			||||||
 | 
					        'access_denied': access_denied,
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -125,6 +178,13 @@ def lookup_representative_by_national_code(request):
 | 
				
			||||||
def create_request_with_entities(request):
 | 
					def create_request_with_entities(request):
 | 
				
			||||||
    """ایجاد/بهروزرسانی چاه و نماینده و سپس ایجاد درخواست"""
 | 
					    """ایجاد/بهروزرسانی چاه و نماینده و سپس ایجاد درخواست"""
 | 
				
			||||||
    User = get_user_model()
 | 
					    User = get_user_model()
 | 
				
			||||||
 | 
					    # Only BROKER can create requests
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        if not (hasattr(request.user, 'profile') and request.user.profile.has_role(UserRoles.BROKER)):
 | 
				
			||||||
 | 
					            return JsonResponse({'ok': False, 'error': 'فقط کارگزار مجاز به ایجاد درخواست است'}, status=403)
 | 
				
			||||||
 | 
					    except Exception:
 | 
				
			||||||
 | 
					        return JsonResponse({'ok': False, 'error': 'فقط کارگزار مجاز به ایجاد درخواست است'}, status=403)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
    process_id = request.POST.get('process')
 | 
					    process_id = request.POST.get('process')
 | 
				
			||||||
    process = Process.objects.get(id=process_id)
 | 
					    process = Process.objects.get(id=process_id)
 | 
				
			||||||
    description = request.POST.get('description', '')
 | 
					    description = request.POST.get('description', '')
 | 
				
			||||||
| 
						 | 
					@ -230,6 +290,14 @@ def create_request_with_entities(request):
 | 
				
			||||||
            well.broker = current_profile.broker
 | 
					            well.broker = current_profile.broker
 | 
				
			||||||
        well.save()
 | 
					        well.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Ensure no active (non-deleted, non-completed) request exists for this well
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        active_exists = ProcessInstance.objects.filter(well=well, is_deleted=False).exclude(status='completed').exists()
 | 
				
			||||||
 | 
					        if active_exists:
 | 
				
			||||||
 | 
					            return JsonResponse({'ok': False, 'error': 'برای این چاه یک درخواست جاری وجود دارد. ابتدا آن را تکمیل یا حذف کنید.'}, status=400)
 | 
				
			||||||
 | 
					    except Exception:
 | 
				
			||||||
 | 
					        return JsonResponse({'ok': False, 'error': 'خطا در بررسی وضعیت درخواستهای قبلی این چاه'}, status=400)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Create request instance
 | 
					    # Create request instance
 | 
				
			||||||
    instance = ProcessInstance.objects.create(
 | 
					    instance = ProcessInstance.objects.create(
 | 
				
			||||||
        process=process,
 | 
					        process=process,
 | 
				
			||||||
| 
						 | 
					@ -261,7 +329,17 @@ def create_request_with_entities(request):
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def delete_request(request, instance_id):
 | 
					def delete_request(request, instance_id):
 | 
				
			||||||
    """حذف درخواست"""
 | 
					    """حذف درخواست"""
 | 
				
			||||||
    instance = get_object_or_404(ProcessInstance, id=instance_id)
 | 
					    instance = get_scoped_instance_or_404(request, instance_id)
 | 
				
			||||||
 | 
					    # Only BROKER can delete requests and only within their scope
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        profile = getattr(request.user, 'profile', None)
 | 
				
			||||||
 | 
					        if not (profile and profile.has_role(UserRoles.BROKER)):
 | 
				
			||||||
 | 
					            return JsonResponse({'success': False, 'message': 'فقط کارگزار مجاز به حذف درخواست است'}, status=403)
 | 
				
			||||||
 | 
					        # Enforce ownership by broker (prevent deleting others' requests)
 | 
				
			||||||
 | 
					        if instance.broker_id and profile.broker and instance.broker_id != profile.broker.id:
 | 
				
			||||||
 | 
					            return JsonResponse({'success': False, 'message': 'شما مجاز به حذف این درخواست نیستید'}, status=403)
 | 
				
			||||||
 | 
					    except Exception:
 | 
				
			||||||
 | 
					        return JsonResponse({'success': False, 'message': 'فقط کارگزار مجاز به حذف درخواست است'}, status=403)
 | 
				
			||||||
    code = instance.code
 | 
					    code = instance.code
 | 
				
			||||||
    if instance.status == 'completed':
 | 
					    if instance.status == 'completed':
 | 
				
			||||||
        return JsonResponse({
 | 
					        return JsonResponse({
 | 
				
			||||||
| 
						 | 
					@ -278,10 +356,10 @@ def delete_request(request, instance_id):
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def step_detail(request, instance_id, step_id):
 | 
					def step_detail(request, instance_id, step_id):
 | 
				
			||||||
    """نمایش جزئیات مرحله خاص"""
 | 
					    """نمایش جزئیات مرحله خاص"""
 | 
				
			||||||
    instance = get_object_or_404(
 | 
					    # Enforce scoped access to prevent URL tampering
 | 
				
			||||||
        ProcessInstance.objects.select_related('process', 'well', 'requester', 'representative', 'representative__profile'),
 | 
					    instance = get_scoped_instance_or_404(request, instance_id)
 | 
				
			||||||
        id=instance_id
 | 
					    # Prefetch for performance
 | 
				
			||||||
    )
 | 
					    instance = ProcessInstance.objects.select_related('process', 'well', 'requester', 'representative', 'representative__profile').get(id=instance.id)
 | 
				
			||||||
    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
					    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
				
			||||||
    # If the request is already completed, redirect to read-only summary page
 | 
					    # If the request is already completed, redirect to read-only summary page
 | 
				
			||||||
    if instance.status == 'completed':
 | 
					    if instance.status == 'completed':
 | 
				
			||||||
| 
						 | 
					@ -339,7 +417,8 @@ def step_detail(request, instance_id, step_id):
 | 
				
			||||||
@login_required 
 | 
					@login_required 
 | 
				
			||||||
def instance_steps(request, instance_id):
 | 
					def instance_steps(request, instance_id):
 | 
				
			||||||
    """هدایت به مرحله فعلی instance"""
 | 
					    """هدایت به مرحله فعلی instance"""
 | 
				
			||||||
    instance = get_object_or_404(ProcessInstance, id=instance_id)
 | 
					    # Enforce scoped access to prevent URL tampering
 | 
				
			||||||
 | 
					    instance = get_scoped_instance_or_404(request, instance_id)
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    if not instance.current_step:
 | 
					    if not instance.current_step:
 | 
				
			||||||
        # اگر مرحله فعلی تعریف نشده، به اولین مرحله برو
 | 
					        # اگر مرحله فعلی تعریف نشده، به اولین مرحله برو
 | 
				
			||||||
| 
						 | 
					@ -361,6 +440,9 @@ def instance_steps(request, instance_id):
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def instance_summary(request, instance_id):
 | 
					def instance_summary(request, instance_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('well', 'representative'), id=instance_id)
 | 
					    instance = get_object_or_404(ProcessInstance.objects.select_related('well', 'representative'), id=instance_id)
 | 
				
			||||||
    # Only show for completed requests; otherwise route to steps
 | 
					    # Only show for completed requests; otherwise route to steps
 | 
				
			||||||
    if instance.status != 'completed':
 | 
					    if instance.status != 'completed':
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
{% load static %}
 | 
					{% load static %}
 | 
				
			||||||
 | 
					{% load accounts_tags %}
 | 
				
			||||||
<!-- Menu -->
 | 
					<!-- Menu -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<aside id="layout-menu" class="layout-menu menu-vertical menu bg-menu-theme">
 | 
					<aside id="layout-menu" class="layout-menu menu-vertical menu bg-menu-theme">
 | 
				
			||||||
| 
						 | 
					@ -108,9 +109,12 @@
 | 
				
			||||||
      <a href="{% url 'processes:request_list' %}" class="menu-link">
 | 
					      <a href="{% url 'processes:request_list' %}" class="menu-link">
 | 
				
			||||||
        <i class="menu-icon tf-icons bx bx-user"></i>
 | 
					        <i class="menu-icon tf-icons bx bx-user"></i>
 | 
				
			||||||
        <div class="text-truncate">درخواستها</div>
 | 
					        <div class="text-truncate">درخواستها</div>
 | 
				
			||||||
 | 
					        {% load processes_tags %}
 | 
				
			||||||
 | 
					        <span class="badge badge-center rounded-pill bg-danger ms-auto">{% incomplete_requests_count request.user %}</span>
 | 
				
			||||||
      </a>
 | 
					      </a>
 | 
				
			||||||
    </li>
 | 
					    </li>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  {% if request.user|is_admin or request.user|is_broker or request.user|is_manager or request.user|is_accountant %}
 | 
				
			||||||
    <!-- Customers -->
 | 
					    <!-- Customers -->
 | 
				
			||||||
    <li class="menu-header small text-uppercase">
 | 
					    <li class="menu-header small text-uppercase">
 | 
				
			||||||
      <span class="menu-header-text">مشترکها</span>
 | 
					      <span class="menu-header-text">مشترکها</span>
 | 
				
			||||||
| 
						 | 
					@ -131,11 +135,11 @@
 | 
				
			||||||
        <div class="text-truncate">چاهها</div>
 | 
					        <div class="text-truncate">چاهها</div>
 | 
				
			||||||
      </a>
 | 
					      </a>
 | 
				
			||||||
    </li>
 | 
					    </li>
 | 
				
			||||||
 | 
					  {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- Apps & Pages -->
 | 
					    <!-- Apps & Pages -->
 | 
				
			||||||
    <li class="menu-header small text-uppercase">
 | 
					    <li class="menu-header small text-uppercase d-none">
 | 
				
			||||||
      <span class="menu-header-text">گزارشها</span>
 | 
					      <span class="menu-header-text">گزارشها</span>
 | 
				
			||||||
    </li>
 | 
					    </li>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,17 +7,23 @@ from django.contrib import messages
 | 
				
			||||||
from django import forms
 | 
					from django import forms
 | 
				
			||||||
from .models import Well, WaterMeterManufacturer
 | 
					from .models import Well, WaterMeterManufacturer
 | 
				
			||||||
from .forms import WellForm, WaterMeterManufacturerForm
 | 
					from .forms import WellForm, WaterMeterManufacturerForm
 | 
				
			||||||
 | 
					from django.contrib.auth.decorators import login_required
 | 
				
			||||||
 | 
					from common.decorators import allowed_roles
 | 
				
			||||||
 | 
					from common.consts import UserRoles
 | 
				
			||||||
 | 
					from processes.utils import scope_wells_queryset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					@allowed_roles([UserRoles.ADMIN, UserRoles.BROKER, UserRoles.MANAGER, UserRoles.ACCOUNTANT])
 | 
				
			||||||
def well_list(request):
 | 
					def well_list(request):
 | 
				
			||||||
    """نمایش لیست چاهها"""
 | 
					    """نمایش لیست چاهها"""
 | 
				
			||||||
    wells = Well.objects.select_related(
 | 
					    base = Well.objects.select_related(
 | 
				
			||||||
        'representative', 
 | 
					        'representative', 
 | 
				
			||||||
        'water_meter_manufacturer',
 | 
					        'water_meter_manufacturer',
 | 
				
			||||||
        'affairs',
 | 
					        'affairs',
 | 
				
			||||||
        'county', 
 | 
					        'county', 
 | 
				
			||||||
        'broker'
 | 
					        'broker'
 | 
				
			||||||
    ).filter(is_deleted=False)
 | 
					    ).filter(is_deleted=False)
 | 
				
			||||||
 | 
					    wells = scope_wells_queryset(request.user, base)
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    # فرم برای افزودن چاه جدید
 | 
					    # فرم برای افزودن چاه جدید
 | 
				
			||||||
    form = WellForm()
 | 
					    form = WellForm()
 | 
				
			||||||
| 
						 | 
					@ -31,6 +37,8 @@ def well_list(request):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@require_POST
 | 
					@require_POST
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					@allowed_roles([UserRoles.ADMIN, UserRoles.BROKER, UserRoles.MANAGER, UserRoles.ACCOUNTANT])
 | 
				
			||||||
def add_well_ajax(request):
 | 
					def add_well_ajax(request):
 | 
				
			||||||
    """AJAX endpoint for adding wells"""
 | 
					    """AJAX endpoint for adding wells"""
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
| 
						 | 
					@ -87,6 +95,8 @@ def add_well_ajax(request):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@require_POST
 | 
					@require_POST
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					@allowed_roles([UserRoles.ADMIN, UserRoles.BROKER, UserRoles.MANAGER, UserRoles.ACCOUNTANT])
 | 
				
			||||||
def edit_well_ajax(request, well_id):
 | 
					def edit_well_ajax(request, well_id):
 | 
				
			||||||
    """AJAX endpoint for editing wells"""
 | 
					    """AJAX endpoint for editing wells"""
 | 
				
			||||||
    well = get_object_or_404(Well, id=well_id)
 | 
					    well = get_object_or_404(Well, id=well_id)
 | 
				
			||||||
| 
						 | 
					@ -141,6 +151,8 @@ def edit_well_ajax(request, well_id):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@require_POST
 | 
					@require_POST
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					@allowed_roles([UserRoles.ADMIN, UserRoles.BROKER, UserRoles.MANAGER, UserRoles.ACCOUNTANT])
 | 
				
			||||||
def delete_well(request, well_id):
 | 
					def delete_well(request, well_id):
 | 
				
			||||||
    """حذف چاه"""
 | 
					    """حذف چاه"""
 | 
				
			||||||
    well = get_object_or_404(Well, id=well_id)
 | 
					    well = get_object_or_404(Well, id=well_id)
 | 
				
			||||||
| 
						 | 
					@ -154,6 +166,7 @@ def delete_well(request, well_id):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@require_GET
 | 
					@require_GET
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
def get_well_data(request, well_id):
 | 
					def get_well_data(request, well_id):
 | 
				
			||||||
    """دریافت اطلاعات چاه برای ویرایش"""
 | 
					    """دریافت اطلاعات چاه برای ویرایش"""
 | 
				
			||||||
    well = get_object_or_404(Well, id=well_id)
 | 
					    well = get_object_or_404(Well, id=well_id)
 | 
				
			||||||
| 
						 | 
					@ -183,6 +196,7 @@ def get_well_data(request, well_id):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@require_POST
 | 
					@require_POST
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
def create_water_meter_manufacturer(request):
 | 
					def create_water_meter_manufacturer(request):
 | 
				
			||||||
    """ایجاد شرکت سازنده کنتور آب جدید"""
 | 
					    """ایجاد شرکت سازنده کنتور آب جدید"""
 | 
				
			||||||
    form = WaterMeterManufacturerForm(request.POST)
 | 
					    form = WaterMeterManufacturerForm(request.POST)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue