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