From 35799b77542bddf5d3769e610045c3886b05c155 Mon Sep 17 00:00:00 2001 From: aminhashemi92 Date: Fri, 5 Sep 2025 13:35:33 +0330 Subject: [PATCH 1/2] Add confirmation and summary --- _base/settings.py | 2 +- certificates/templates/certificates/step.html | 7 +- certificates/views.py | 14 +- .../templates/contracts/contract_step.html | 62 ++-- contracts/views.py | 22 +- db.sqlite3 | Bin 1970176 -> 2314240 bytes .../installation_assign_step.html | 32 +- .../installation_report_step.html | 148 +++++++- installations/views.py | 131 +++++++- invoices/admin.py | 4 +- .../invoices/final_invoice_step.html | 14 +- .../invoices/final_settlement_step.html | 219 +++++++++--- .../invoices/quote_payment_step.html | 264 ++++++++------- .../invoices/quote_preview_step.html | 28 +- invoices/templates/invoices/quote_step.html | 61 ++-- invoices/views.py | 316 +++++++++++++++++- processes/admin.py | 16 +- ...02_stepapproval_stepapproverrequirement.py | 48 +++ processes/models.py | 58 ++++ .../processes/includes/stepper_header.html | 5 +- .../templates/processes/instance_summary.html | 168 ++++++++++ .../templates/processes/request_list.html | 12 +- processes/templatetags/processes_tags.py | 6 +- processes/urls.py | 1 + processes/views.py | 46 +++ 25 files changed, 1419 insertions(+), 265 deletions(-) create mode 100644 processes/migrations/0002_stepapproval_stepapproverrequirement.py create mode 100644 processes/templates/processes/instance_summary.html diff --git a/_base/settings.py b/_base/settings.py index e53a6f1..8261409 100644 --- a/_base/settings.py +++ b/_base/settings.py @@ -167,7 +167,7 @@ JAZZMIN_SETTINGS = { # Copyright on the footer "copyright": "سامانه شفافیت", # Logo to use for your site, must be present in static files, used for brand on top left - "site_logo": "../static/dist/img/iconlogo.png", + # "site_logo": "../static/dist/img/iconlogo.png", # Relative paths to custom CSS/JS scripts (must be present in static files) "custom_css": "../static/admin/css/custom_rtl.css", "custom_js": None, diff --git a/certificates/templates/certificates/step.html b/certificates/templates/certificates/step.html index 3392f82..b8923c2 100644 --- a/certificates/templates/certificates/step.html +++ b/certificates/templates/certificates/step.html @@ -2,6 +2,7 @@ {% load static %} {% load processes_tags %} {% load humanize %} + {% load accounts_tags %} {% block sidebar %} {% include 'sidebars/admin.html' %} @@ -79,7 +80,11 @@ {% else %}{% endif %}
{% csrf_token %} - + {% if request.user|is_broker %} + + {% else %} + + {% endif %}
diff --git a/certificates/views.py b/certificates/views.py index 428c5ba..761ee83 100644 --- a/certificates/views.py +++ b/certificates/views.py @@ -9,6 +9,7 @@ from processes.models import ProcessInstance, StepInstance from invoices.models import Invoice from installations.models import InstallationReport from .models import CertificateTemplate, CertificateInstance +from common.consts import UserRoles from _helpers.jalali import Gregorian @@ -78,6 +79,14 @@ def certificate_step(request, instance_id, step_id): next_step = instance.process.steps.filter(order__gt=instance.current_step.order).first() if instance.current_step else None if request.method == 'POST': + # Only broker can approve and finish certificate step + try: + if not (hasattr(request.user, 'profile') and request.user.profile.has_role(UserRoles.BROKER)): + messages.error(request, 'شما مجوز تایید این مرحله را ندارید') + return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id) + except Exception: + messages.error(request, 'شما مجوز تایید این مرحله را ندارید') + return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id) cert.approved = True cert.approved_at = timezone.now() cert.save() @@ -89,7 +98,10 @@ def certificate_step(request, instance_id, step_id): instance.current_step = next_step instance.save() return redirect('processes:step_detail', instance_id=instance.id, step_id=next_step.id) - return redirect('processes:request_list') + # Mark the whole process instance as completed on the last step + instance.status = 'completed' + instance.save() + return redirect('processes:instance_summary', instance_id=instance.id) return render(request, 'certificates/step.html', { 'instance': instance, diff --git a/contracts/templates/contracts/contract_step.html b/contracts/templates/contracts/contract_step.html index 33ff326..df4fdfc 100644 --- a/contracts/templates/contracts/contract_step.html +++ b/contracts/templates/contracts/contract_step.html @@ -41,32 +41,36 @@
- {% if template.company.logo %} -
- لوگوی شرکت -

{{ contract.template.company.name }}

-
{{ contract.template.name }}
-
- {% endif %} + {% if can_view_contract_body %} + {% if template.company.logo %} +
+ لوگوی شرکت +

{{ contract.template.company.name }}

+
{{ contract.template.name }}
+
+ {% endif %} -
تاریخ: {{ contract.jcreated }}
-
-
{{ contract.rendered_body|safe }}
-
-
-
-
امضای مشترک
-
-
-
-
امضای شرکت
-
- {% if template.company.signature %} - امضای شرکت - {% endif %} +
تاریخ: {{ contract.jcreated }}
+
+
{{ contract.rendered_body|safe }}
+
+
+
+
امضای مشترک
+
+
+
+
امضای شرکت
+
+ {% if template.company.signature %} + امضای شرکت + {% endif %} +
-
+ {% else %} +
شما دسترسی به مشاهده متن قرارداد را ندارید.
+ {% endif %}
@@ -77,9 +81,17 @@ {% endif %} {% if next_step %} - + {% if is_broker %} + + {% else %} + بعدی + {% endif %} {% else %} - + {% if is_broker %} + + {% else %} + + {% endif %} {% endif %}
diff --git a/contracts/views.py b/contracts/views.py index f2d0deb..1949665 100644 --- a/contracts/views.py +++ b/contracts/views.py @@ -4,6 +4,7 @@ from django.urls import reverse from django.utils import timezone from django.template import Template, Context from processes.models import ProcessInstance, StepInstance +from common.consts import UserRoles from .models import ContractTemplate, ContractInstance from _helpers.utils import jalali_converter2 @@ -34,6 +35,20 @@ def contract_step(request, instance_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() next_step = instance.process.steps.filter(order__gt=step.order).first() + # Access control: + # - INSTALLER: can open step but cannot view contract body (show inline message) + # - Others: can view + # - Only BROKER can submit/complete this step + profile = getattr(request.user, 'profile', None) + is_broker = False + can_view_contract_body = True + try: + is_broker = bool(profile and profile.has_role(UserRoles.BROKER)) + if profile and profile.has_role(UserRoles.INSTALLER): + can_view_contract_body = False + except Exception: + pass + template_obj = ContractTemplate.objects.first() if not template_obj: return render(request, 'contracts/contract_missing.html', {'instance': instance}) @@ -54,8 +69,11 @@ def contract_step(request, instance_id, step_id): contract.rendered_body = rendered contract.save() - # If user submits to go next, mark this step completed and go to next + # If user submits to go next, only broker can complete and go to next if request.method == 'POST': + if not is_broker: + from django.http import JsonResponse + return JsonResponse({'success': False, 'message': 'شما مجوز تایید این مرحله را ندارید'}, status=403) StepInstance.objects.update_or_create( process_instance=instance, step=step, @@ -74,6 +92,8 @@ def contract_step(request, instance_id, step_id): 'template': template_obj, 'previous_step': previous_step, 'next_step': next_step, + 'is_broker': is_broker, + 'can_view_contract_body': can_view_contract_body, }) diff --git a/db.sqlite3 b/db.sqlite3 index 805325d89972f6dd0dd0e343788ad48746fd0faa..d0474e889234d6c65d345ece284a7c06eca8a071 100644 GIT binary patch delta 251794 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 MnU&J1qCQ>uKhLdgu>b%7 delta 16730 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 diff --git a/installations/templates/installations/installation_assign_step.html b/installations/templates/installations/installation_assign_step.html index c1d6c79..2bdbe4d 100644 --- a/installations/templates/installations/installation_assign_step.html +++ b/installations/templates/installations/installation_assign_step.html @@ -1,6 +1,7 @@ {% extends '_base.html' %} {% load static %} {% load processes_tags %} +{% load common_tags %} {% load humanize %} {% block sidebar %} @@ -41,12 +42,15 @@
+ {% if show_denied_msg %} +
شما اجازه تعیین نصاب را ندارید.
+ {% endif %}
{% csrf_token %}
- {% for p in installers %} @@ -55,17 +59,39 @@
- +
+ {% if assignment.assigned_by or assignment.installer %} +
+
+ {% if assignment.assigned_by %} +
+
تعیین‌کننده نصاب
+
{{ assignment.assigned_by.get_full_name|default:assignment.assigned_by.username }} ({{ assignment.assigned_by.username }})
+
+ {% endif %} + {% if assignment.updated %} +
+
تاریخ ثبت/ویرایش
+
{{ assignment.updated|to_jalali }}
+
+ {% endif %} +
+
+ {% endif %}
{% if previous_step %} قبلی {% else %} {% endif %} - + {% if is_manager %} + + {% else %} + بعدی + {% endif %}
diff --git a/installations/templates/installations/installation_report_step.html b/installations/templates/installations/installation_report_step.html index 275f7bc..16b1eb6 100644 --- a/installations/templates/installations/installation_report_step.html +++ b/installations/templates/installations/installation_report_step.html @@ -2,6 +2,7 @@ {% load static %} {% load processes_tags %} {% load common_tags %} +{% load accounts_tags %} {% load humanize %} {% block sidebar %} @@ -41,13 +42,31 @@ {% stepper_header instance step %}
- {% if report and not edit_mode %}
-
- ویرایش گزارش نصب +
+
+ {% if request.user|is_installer %} + ویرایش گزارش نصب + {% else %} + + {% endif %} + {% if user_can_approve %} + + + {% endif %} +
+ {% if step_instance and step_instance.status == 'rejected' and step_instance.get_latest_rejection %} + + {% endif %}

تاریخ مراجعه: {{ report.visited_date|to_jalali|default:'-' }}

@@ -67,6 +86,9 @@
{% endif %}
+ {% if request.user|is_manager or request.user|is_admin %} +
+ {% endif %}
عکس‌ها
{% for p in report.photos.all %} @@ -115,6 +137,42 @@
+ {% if approver_statuses %} +
+
+
وضعیت تاییدها
+ {% if user_can_approve %} +
+ + +
+ {% endif %} +
+
+
+ {% for st in approver_statuses %} +
+
+
+ {{ st.role.name }} + {% if st.status == 'approved' %} + تایید شد + {% elif st.status == 'rejected' %} + رد شد + {% else %} + در انتظار + {% endif %} +
+ {% if st.status == 'rejected' and st.reason %} +
علت: {{ st.reason }}
+ {% endif %} +
+
+ {% endfor %} +
+
+
+ {% endif %}
{% if previous_step %} @@ -127,6 +185,9 @@ {% endif %}
{% else %} + {% if not request.user|is_installer %} +
شما مجوز ثبت/ویرایش گزارش نصب را ندارید. اطلاعات به صورت فقط خواندنی نمایش داده می‌شود.
+ {% endif %}
{% csrf_token %}
@@ -134,40 +195,42 @@
- +
- +
- +
- +
- +
- +
- +
- + {% if request.user|is_installer %} + + {% endif %}
{% if report %}
@@ -175,7 +238,9 @@
photo - + {% if request.user|is_installer %} + + {% endif %}
@@ -285,7 +350,11 @@ {% endif %}
- + {% if request.user|is_installer %} + + {% else %} + + {% endif %} {% if next_step %} بعدی {% endif %} @@ -298,6 +367,58 @@
+ + + + + + + + + {% endblock %} {% block script %} @@ -445,4 +566,3 @@ {% endblock %} - diff --git a/installations/views.py b/installations/views.py index 4692886..8c3dc7e 100644 --- a/installations/views.py +++ b/installations/views.py @@ -5,7 +5,8 @@ from django.urls import reverse from django.utils import timezone from accounts.models import Profile from common.consts import UserRoles -from processes.models import ProcessInstance, StepInstance +from processes.models import ProcessInstance, StepInstance, StepRejection, StepApproval +from accounts.models import Role from invoices.models import Item, Quote, QuoteItem from .models import InstallationAssignment, InstallationReport, InstallationPhoto, InstallationItemChange from decimal import Decimal, InvalidOperation @@ -21,7 +22,18 @@ def installation_assign_step(request, instance_id, step_id): installers = Profile.objects.filter(roles__slug=UserRoles.INSTALLER.value).select_related('user').all() assignment, _ = InstallationAssignment.objects.get_or_create(process_instance=instance) + # Role flags + profile = getattr(request.user, 'profile', None) + is_manager = False + try: + is_manager = bool(profile and profile.has_role(UserRoles.MANAGER)) + except Exception: + is_manager = False + if request.method == 'POST': + if not is_manager: + messages.error(request, 'شما اجازه تعیین نصاب را ندارید') + return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id) installer_id = request.POST.get('installer_id') scheduled_date = (request.POST.get('scheduled_date') or '').strip() assignment.installer_id = installer_id or None @@ -43,6 +55,10 @@ def installation_assign_step(request, instance_id, step_id): return redirect('processes:step_detail', instance_id=instance.id, step_id=next_step.id) return redirect('processes:request_list') + # Read-only logic for non-managers + read_only = not is_manager + show_denied_msg = (not is_manager) and (assignment.installer_id is None) + return render(request, 'installations/installation_assign_step.html', { 'instance': instance, 'step': step, @@ -50,6 +66,9 @@ def installation_assign_step(request, instance_id, step_id): 'installers': installers, 'previous_step': previous_step, 'next_step': next_step, + 'is_manager': is_manager, + 'read_only': read_only, + 'show_denied_msg': show_denied_msg, }) @@ -61,15 +80,94 @@ def installation_report_step(request, instance_id, step_id): next_step = instance.process.steps.filter(order__gt=step.order).first() assignment = InstallationAssignment.objects.filter(process_instance=instance).first() existing_report = InstallationReport.objects.filter(assignment=assignment).order_by('-created').first() - edit_mode = True if request.GET.get('edit') == '1' else False - print("edit_mode", edit_mode) + # Only installers can enter edit mode + user_is_installer = hasattr(request.user, 'profile') and request.user.profile.has_role(UserRoles.INSTALLER) + edit_mode = True if (request.GET.get('edit') == '1' and user_is_installer) else False # current quote items baseline quote = Quote.objects.filter(process_instance=instance).first() quote_items = list(quote.items.select_related('item').all()) if quote else [] quote_price_map = {qi.item_id: qi.unit_price for qi in quote_items} - items = Item.objects.all().order_by('name') + items = Item.objects.filter(is_active=True, is_special=False, is_deleted=False).order_by('name') + + # Ensure a StepInstance exists for this step + step_instance, _ = StepInstance.objects.get_or_create( + process_instance=instance, + step=step, + defaults={'status': 'in_progress'} + ) + + # Build approver requirements/status for UI + reqs = list(step.approver_requirements.select_related('role').all()) + user_roles_qs = getattr(getattr(request.user, 'profile', None), 'roles', None) + user_roles = list(user_roles_qs.all()) if user_roles_qs is not None else [] + user_can_approve = any(r.role in user_roles for r in reqs) + approvals_list = list(step_instance.approvals.select_related('role').all()) + approvals_by_role = {a.role_id: a for a in approvals_list} + approver_statuses = [ + { + 'role': r.role, + 'status': (approvals_by_role.get(r.role_id).decision if approvals_by_role.get(r.role_id) else None), + 'reason': (approvals_by_role.get(r.role_id).reason if approvals_by_role.get(r.role_id) else ''), + } + for r in reqs + ] + + # Manager approval/rejection actions + if request.method == 'POST' and request.POST.get('action') in ['approve', 'reject']: + action = request.POST.get('action') + # find a matching approver role based on step requirements + req_roles = [req.role for req in step.approver_requirements.select_related('role').all()] + user_roles = list(getattr(getattr(request.user, 'profile', None), 'roles', Role.objects.none()).all()) + matching_role = next((r for r in user_roles if r in req_roles), None) + if matching_role is None: + messages.error(request, 'شما دسترسی لازم برای این عملیات را ندارید.') + return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id) + + if not existing_report: + messages.error(request, 'گزارش برای تایید/رد وجود ندارد.') + return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id) + + if action == 'approve': + existing_report.approved = True + existing_report.save() + StepApproval.objects.update_or_create( + step_instance=step_instance, + role=matching_role, + defaults={'approved_by': request.user, 'decision': 'approved', 'reason': ''} + ) + if step_instance.is_fully_approved(): + step_instance.status = 'completed' + step_instance.completed_at = timezone.now() + step_instance.save() + if next_step: + instance.current_step = next_step + instance.save() + return redirect('processes:step_detail', instance_id=instance.id, step_id=next_step.id) + return redirect('processes:request_list') + messages.success(request, 'تایید شما ثبت شد. منتظر تایید سایر نقش‌ها.') + return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id) + + if action == 'reject': + reason = (request.POST.get('reject_reason') or '').strip() + if not reason: + messages.error(request, 'لطفاً علت رد شدن را وارد کنید.') + return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id) + StepApproval.objects.update_or_create( + step_instance=step_instance, + role=matching_role, + defaults={'approved_by': request.user, 'decision': 'rejected', 'reason': reason} + ) + StepRejection.objects.create(step_instance=step_instance, rejected_by=request.user, reason=reason) + existing_report.approved = False + existing_report.save() + messages.success(request, 'گزارش رد شد و برای اصلاح به نصاب بازگشت.') + return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id) if request.method == 'POST': + # Only installers can submit or edit reports (non-approval actions) + if request.POST.get('action') not in ['approve', 'reject'] and not user_is_installer: + messages.error(request, 'شما مجوز ثبت/ویرایش گزارش نصب را ندارید') + return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id) description = (request.POST.get('description') or '').strip() visited_date = (request.POST.get('visited_date') or '').strip() if '/' in visited_date: @@ -134,6 +232,7 @@ def installation_report_step(request, instance_id, step_id): report.is_meter_suspicious = is_suspicious report.utm_x = utm_x report.utm_y = utm_y + report.approved = False # back to awaiting approval after edits report.save() # delete selected existing photos for key, val in request.POST.items(): @@ -211,18 +310,17 @@ def installation_report_step(request, instance_id, step_id): total_price=total, ) - # complete step - StepInstance.objects.update_or_create( - process_instance=instance, - step=step, - defaults={'status': 'completed', 'completed_at': timezone.now()} - ) + # After installer submits/edits, set step back to in_progress and clear approvals + step_instance.status = 'in_progress' + step_instance.completed_at = None + step_instance.save() + try: + step_instance.approvals.all().delete() + except Exception: + pass - if next_step: - instance.current_step = next_step - instance.save() - return redirect('processes:step_detail', instance_id=instance.id, step_id=next_step.id) - return redirect('processes:request_list') + messages.success(request, 'گزارش ثبت شد و در انتظار تایید است.') + return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id) # Build prefill maps from existing report changes removed_ids = set() @@ -250,6 +348,9 @@ def installation_report_step(request, instance_id, step_id): 'added_map': added_map, 'previous_step': previous_step, 'next_step': next_step, + 'step_instance': step_instance, + 'approver_statuses': approver_statuses, + 'user_can_approve': user_can_approve, }) diff --git a/invoices/admin.py b/invoices/admin.py index 8428a2a..f8a46cb 100644 --- a/invoices/admin.py +++ b/invoices/admin.py @@ -6,8 +6,8 @@ from .models import Item, Quote, QuoteItem, Invoice, InvoiceItem, Payment @admin.register(Item) class ItemAdmin(SimpleHistoryAdmin): - list_display = ['name', 'unit_price', 'default_quantity', 'is_default_in_quotes', 'is_active', 'created_by'] - list_filter = ['is_default_in_quotes', 'is_active', 'created_by'] + list_display = ['name', 'unit_price', 'default_quantity', 'is_default_in_quotes', 'is_special', 'is_active', 'created_by'] + list_filter = ['is_default_in_quotes', 'is_special', 'is_active', 'created_by'] search_fields = ['name', 'description'] prepopulated_fields = {'slug': ('name',)} readonly_fields = ['deleted_at', 'created', 'updated'] diff --git a/invoices/templates/invoices/final_invoice_step.html b/invoices/templates/invoices/final_invoice_step.html index 9376705..dfee339 100644 --- a/invoices/templates/invoices/final_invoice_step.html +++ b/invoices/templates/invoices/final_invoice_step.html @@ -50,7 +50,9 @@
فاکتور نهایی
- + {% if is_manager %} + + {% endif %}
@@ -127,7 +129,9 @@ {{ si.unit_price|floatformat:0|intcomma:False }} {{ si.total_price|floatformat:0|intcomma:False }} - + {% if is_manager %} + + {% endif %} {% endfor %} @@ -164,7 +168,11 @@ {% endif %} {% if next_step %} - + {% if is_manager %} + + {% else %} + بعدی + {% endif %} {% endif %}
diff --git a/invoices/templates/invoices/final_settlement_step.html b/invoices/templates/invoices/final_settlement_step.html index 5058a09..ca11dec 100644 --- a/invoices/templates/invoices/final_settlement_step.html +++ b/invoices/templates/invoices/final_settlement_step.html @@ -2,6 +2,7 @@ {% load static %} {% load processes_tags %} {% load common_tags %} +{% load accounts_tags %} {% load humanize %} {% block sidebar %} @@ -46,6 +47,7 @@
+ {% if is_broker %}
ثبت تراکنش تسویه
@@ -78,11 +80,11 @@
- +
- +
@@ -92,23 +94,39 @@
-
+ {% endif %} +
-
وضعیت فاکتور
+
+
وضعیت فاکتور
+
-
-
+
+
مبلغ نهایی
{{ invoice.final_amount|floatformat:0|intcomma:False }} تومان
-
-
+
+
+
پرداختی‌ها
+
{{ invoice.paid_amount|floatformat:0|intcomma:False }} تومان
+
+
+
+
مانده
{{ invoice.remaining_amount|floatformat:0|intcomma:False }} تومان
+
+ {% if invoice.remaining_amount <= 0 %} + تسویه کامل + {% else %} + باقی‌مانده دارد + {% endif %} +
@@ -123,8 +141,8 @@ مبلغ تاریخ روش - شماره مرجع - عملیات + شماره مرجع/چک + عملیات @@ -132,7 +150,7 @@ {% if p.direction == 'in' %}دریافتی{% else %}پرداختی{% endif %} {{ p.amount|floatformat:0|intcomma:False }} تومان - {{ p.payment_date|to_jalali }} + {{ p.payment_date|date:'Y/m/d' }} {{ p.get_payment_method_display }} {{ p.reference_number|default:'-' }} @@ -142,7 +160,9 @@ {% endif %} - + {% if is_broker %} + + {% endif %}
@@ -152,20 +172,141 @@
- +
+ {% if approver_statuses %} +
+
+
وضعیت تاییدها
+ {% if can_approve_reject %} +
+ + +
+ {% endif %} +
+
+
+ {% for st in approver_statuses %} +
+
+
+ {{ st.role.name }} + {% if st.status == 'approved' %} + تایید شد + {% elif st.status == 'rejected' %} + رد شد + {% else %} + در انتظار + {% endif %} +
+ {% if st.status == 'rejected' and st.reason %} +
علت: {{ st.reason }}
+ {% endif %} +
+
+ {% endfor %} +
+
+
+ {% endif %} +
+ {% if previous_step %} + قبلی + {% else %} + + {% endif %} + {% if step_instance.status == 'completed' %} + {% if next_step %} + بعدی + {% else %} + اتمام + {% endif %} + {% endif %} +
+ + + + + + + + + + + + {% endblock %} {% block script %} @@ -191,8 +332,11 @@ if (g) { fd.set('payment_date', g); } return fd; } - document.getElementById('btnAddFinalPayment').addEventListener('click', function(){ - const fd = buildForm(); + (function(){ + const btn = document.getElementById('btnAddFinalPayment'); + if (!btn) return; + btn.addEventListener('click', function(){ + const fd = buildForm(); // Frontend validation const amount = document.getElementById('id_amount').value.trim(); const payDate = document.getElementById('id_payment_date').value.trim(); @@ -204,7 +348,7 @@ showToast('همه فیلدها الزامی است', 'danger'); return; } - fetch('{% url "invoices:add_final_payment" instance.id step.id %}', { method:'POST', body: fd }) + fetch('{% url "invoices:add_final_payment" instance.id step.id %}', { method:'POST', body: fd }) .then(r=>r.json()).then(resp=>{ if (resp.success) { showToast('تراکنش ثبت شد', 'success'); @@ -213,12 +357,20 @@ showToast(resp.message || 'خطا در ثبت تراکنش', 'danger'); } }).catch(()=> showToast('خطا در ارتباط با سرور', 'danger')); - }); + }); + })(); - function deleteFinalPayment(id){ + let deleteTargetId = null; + function openDeleteModal(id){ + deleteTargetId = id; + const modal = new bootstrap.Modal(document.getElementById('deletePaymentModal')); + modal.show(); + } + function confirmDeletePayment(){ + if (!deleteTargetId) return; const fd = new FormData(); fd.append('csrfmiddlewaretoken', document.querySelector('input[name=csrfmiddlewaretoken]').value); - fetch(`{% url "invoices:delete_final_payment" instance.id step.id 0 %}`.replace('/0/', `/${id}/`), { method:'POST', body: fd }) + fetch(`{% url "invoices:delete_final_payment" instance.id step.id 0 %}`.replace('/0/', `/${deleteTargetId}/`), { method:'POST', body: fd }) .then(r=>r.json()).then(resp=>{ if (resp.success) { showToast('حذف شد', 'success'); @@ -229,20 +381,7 @@ }).catch(()=> showToast('خطا در ارتباط با سرور', 'danger')); } - document.getElementById('btnApproveFinalSettlement').addEventListener('click', function(){ - const fd = new FormData(); - fd.append('csrfmiddlewaretoken', document.querySelector('input[name=csrfmiddlewaretoken]').value); - fetch('{% url "invoices:approve_final_settlement" instance.id step.id %}', { method:'POST', body: fd }) - .then(r=>r.json()).then(resp=>{ - if (resp.success) { - showToast(resp.message || 'تایید شد', 'success'); - if (resp.redirect) setTimeout(()=>{ window.location.href = resp.redirect; }, 600); - } else { - showToast(resp.message || 'خطا در تایید', 'danger'); - } - }).catch(()=> showToast('خطا در ارتباط با سرور', 'danger')); - }); + // Legacy approve button removed; using modal forms below {% endblock %} - diff --git a/invoices/templates/invoices/quote_payment_step.html b/invoices/templates/invoices/quote_payment_step.html index ab9f518..1b963ee 100644 --- a/invoices/templates/invoices/quote_payment_step.html +++ b/invoices/templates/invoices/quote_payment_step.html @@ -1,6 +1,7 @@ {% extends '_base.html' %} {% load static %} {% load processes_tags %} +{% load accounts_tags %} {% load humanize %} {% block sidebar %} @@ -55,14 +56,15 @@
{{ step.name }}
- ثبت فیش‌های واریزی برای پیش‌فاکتور + ثبت فیش‌ها/چک‌های واریزی برای پیش‌فاکتور
+ {% if can_manage_payments %}
-
ثبت فیش جدید
+
ثبت فیش/چک جدید
@@ -84,11 +86,11 @@
- +
- +
@@ -96,16 +98,16 @@
- +
-
+ {% endif %} +
-
وضعیت پیش‌فاکتور
- مشاهده پیش‌فاکتور +
وضعیت پیش‌فاکتور
@@ -139,8 +141,10 @@
-
-
فیش‌های ثبت شده
+
+
+
فیش‌ها/چک‌های ثبت شده
+
@@ -149,9 +153,8 @@ - - - + + @@ -162,28 +165,23 @@ - {% empty %} - + {% endfor %} @@ -191,6 +189,42 @@ + {% if approver_statuses %} +
+
+
وضعیت تاییدها
+ {% if can_approve_reject %} +
+ + +
+ {% endif %} +
+
+
+ {% for st in approver_statuses %} +
+
+
+ {{ st.role.name }} + {% if st.status == 'approved' %} + تایید شد + {% elif st.status == 'rejected' %} + رد شد + {% else %} + در انتظار + {% endif %} +
+ {% if st.status == 'rejected' and st.reason %} +
علت: {{ st.reason }}
+ {% endif %} +
+
+ {% endfor %} +
+
+
+ {% endif %}
{% if previous_step %} @@ -201,30 +235,114 @@ {% else %} {% endif %} - + {% if step_instance.status == 'completed' %} + {% if next_step %} + + بعدی + + + {% else %} + اتمام + {% endif %} + {% endif %}
+ + + + + + + + + + {% endblock %} {% block script %} @@ -365,42 +439,4 @@ })(); - - - - {% endblock %} diff --git a/invoices/templates/invoices/quote_preview_step.html b/invoices/templates/invoices/quote_preview_step.html index 4ba69f1..e6e405a 100644 --- a/invoices/templates/invoices/quote_preview_step.html +++ b/invoices/templates/invoices/quote_preview_step.html @@ -221,20 +221,32 @@ {% endif %} - {% if step_instance.status == 'completed' %} + {% if is_broker %} + {% if step_instance.status == 'completed' %} + {% if next_step %} + + بعدی + + + {% else %} + + {% endif %} + {% else %} + + {% endif %} + {% else %} {% if next_step %} - بعدی + class="btn btn-label-primary"> + مرحله بعد {% else %} - + اتمام {% endif %} - {% else %} - {% endif %} diff --git a/invoices/templates/invoices/quote_step.html b/invoices/templates/invoices/quote_step.html index 5219c40..ca17747 100644 --- a/invoices/templates/invoices/quote_step.html +++ b/invoices/templates/invoices/quote_step.html @@ -58,6 +58,7 @@ {% endif %}
+ {% if is_broker or existing_quote %}
مبلغ تاریخ روششماره مرجعتصویرعملیاتشماره مرجع/چکعملیات
{{ p.get_payment_method_display }} {{ p.reference_number|default:'-' }} - {% if p.receipt_image %} +
+ {% if p.receipt_image %} - {% else %} - - - {% endif %} -
-
- - + {% endif %}
تا کنون فیشی ثبت نشده استتا کنون فیش/چکی ثبت نشده است
@@ -77,7 +78,8 @@ data-item-id="{{ item.id }}" data-is-default="{% if item.is_default_in_quotes %}1{% else %}0{% endif %}" {% if selected_qty %}checked{% elif item.is_default_in_quotes %}checked{% endif %} - {% if item.is_default_in_quotes %}disabled title="آیتم پیش‌فرض است و قابل حذف نیست"{% endif %}> + {% if item.is_default_in_quotes or not is_broker %}disabled{% endif %} + {% if item.is_default_in_quotes %}title="آیتم پیش‌فرض است و قابل حذف نیست"{% elif not is_broker %}title="فقط کارگزار مجاز به تغییر اقلام است"{% endif %}> {% endwith %} @@ -102,8 +104,9 @@
@@ -86,15 +88,15 @@ پیش‌فرض {% endif %} - {% if item.description %}{{ item.description }}{% endif %}
{{ item.unit_price|floatformat:0|intcomma:False }} تومان - +
- - + {% else %} +
شما دسترسی به ثبت اقلام ندارید.
+ {% endif %}
@@ -118,27 +121,35 @@ {% endif %} - {% if step_instance.status == 'completed' %} - {% if next_step %} -
- -
- + {% if is_broker %} + {% if step_instance.status == 'completed' %} + {% if next_step %} +
+ +
+ {% else %} + + {% endif %} {% else %} - + {% endif %} {% else %} - + {% if next_step %} + + مرحله بعد + + + {% else %} + اتمام + {% endif %} {% endif %}
diff --git a/invoices/views.py b/invoices/views.py index b39aafe..6429499 100644 --- a/invoices/views.py +++ b/invoices/views.py @@ -9,7 +9,9 @@ from django.urls import reverse from decimal import Decimal, InvalidOperation import json -from processes.models import ProcessInstance, ProcessStep, StepInstance +from processes.models import ProcessInstance, ProcessStep, StepInstance, StepRejection, StepApproval +from accounts.models import Role +from common.consts import UserRoles from .models import Item, Quote, QuoteItem, Payment, Invoice from installations.models import InstallationReport, InstallationItemChange @@ -28,7 +30,7 @@ def quote_step(request, instance_id, step_id): return redirect('processes:request_list') # دریافت آیتم‌ها - items = Item.objects.all().order_by('name') + items = Item.objects.filter(is_active=True, is_special=False, is_deleted=False).order_by('name') existing_quote = Quote.objects.filter(process_instance=instance).first() existing_quote_items = {} if existing_quote: @@ -40,6 +42,14 @@ def quote_step(request, instance_id, step_id): previous_step = instance.process.steps.filter(order__lt=step.order).last() next_step = instance.process.steps.filter(order__gt=step.order).first() + # determine if current user is broker + profile = getattr(request.user, 'profile', None) + is_broker = False + try: + is_broker = bool(profile and profile.has_role(UserRoles.BROKER)) + except Exception: + is_broker = False + return render(request, 'invoices/quote_step.html', { 'instance': instance, 'step': step, @@ -49,6 +59,7 @@ def quote_step(request, instance_id, step_id): 'existing_quote': existing_quote, 'previous_step': previous_step, 'next_step': next_step, + 'is_broker': is_broker, }) @require_POST @@ -57,6 +68,13 @@ def create_quote(request, instance_id, step_id): """ساخت/بروزرسانی پیش‌فاکتور از اقلام انتخابی""" instance = get_object_or_404(ProcessInstance, id=instance_id) step = get_object_or_404(instance.process.steps, id=step_id) + # enforce permission: only BROKER can create/update quote + profile = getattr(request.user, 'profile', None) + try: + if not (profile and profile.has_role(UserRoles.BROKER)): + return JsonResponse({'success': False, 'message': 'شما مجوز ثبت/ویرایش پیش‌فاکتور را ندارید'}) + except Exception: + return JsonResponse({'success': False, 'message': 'شما مجوز ثبت/ویرایش پیش‌فاکتور را ندارید'}) try: items_payload = json.loads(request.POST.get('items') or '[]') @@ -72,7 +90,7 @@ def create_quote(request, instance_id, step_id): except Exception: continue - default_item_ids = set(Item.objects.filter(is_default_in_quotes=True).values_list('id', flat=True)) + default_item_ids = set(Item.objects.filter(is_default_in_quotes=True, is_deleted=False).values_list('id', flat=True)) if default_item_ids: for default_id in default_item_ids: if default_id not in payload_by_id: @@ -163,6 +181,14 @@ def quote_preview_step(request, instance_id, step_id): previous_step = instance.process.steps.filter(order__lt=step.order).last() next_step = instance.process.steps.filter(order__gt=step.order).first() + # determine if current user is broker for UI controls + profile = getattr(request.user, 'profile', None) + is_broker = False + try: + is_broker = bool(profile and profile.has_role(UserRoles.BROKER)) + except Exception: + is_broker = False + return render(request, 'invoices/quote_preview_step.html', { 'instance': instance, 'step': step, @@ -170,6 +196,7 @@ def quote_preview_step(request, instance_id, step_id): 'quote': quote, 'previous_step': previous_step, 'next_step': next_step, + 'is_broker': is_broker, }) @login_required @@ -190,6 +217,13 @@ def approve_quote(request, instance_id, step_id): instance = get_object_or_404(ProcessInstance, id=instance_id) step = get_object_or_404(instance.process.steps, id=step_id) quote = get_object_or_404(Quote, process_instance=instance) + # enforce permission: only BROKER can approve + profile = getattr(request.user, 'profile', None) + try: + if not (profile and profile.has_role(UserRoles.BROKER)): + return JsonResponse({'success': False, 'message': 'شما مجوز تایید پیش‌فاکتور را ندارید'}) + except Exception: + return JsonResponse({'success': False, 'message': 'شما مجوز تایید پیش‌فاکتور را ندارید'}) # تایید پیش‌فاکتور quote.status = 'sent' @@ -247,7 +281,97 @@ def quote_payment_step(request, instance_id, step_id): 'is_fully_paid': quote.get_remaining_amount() <= 0, } - step_instance = instance.step_instances.filter(step=step).first() + step_instance, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step, defaults={'status': 'in_progress'}) + reqs = list(step.approver_requirements.select_related('role').all()) + user_roles_qs = getattr(getattr(request.user, 'profile', None), 'roles', None) + user_roles = list(user_roles_qs.all()) if user_roles_qs is not None else [] + approvals_list = list(step_instance.approvals.select_related('role').all()) + approvals_by_role = {a.role_id: a for a in approvals_list} + approver_statuses = [ + { + 'role': r.role, + 'status': (approvals_by_role.get(r.role_id).decision if approvals_by_role.get(r.role_id) else None), + 'reason': (approvals_by_role.get(r.role_id).reason if approvals_by_role.get(r.role_id) else ''), + } + for r in reqs + ] + # dynamic permission: who can approve/reject this step (based on requirements) + try: + req_role_ids = {r.role_id for r in reqs} + user_role_ids = {ur.id for ur in user_roles} + can_approve_reject = len(req_role_ids.intersection(user_role_ids)) > 0 + except Exception: + can_approve_reject = False + # approver status map for template + reqs = list(step.approver_requirements.select_related('role').all()) + user_roles_qs = getattr(getattr(request.user, 'profile', None), 'roles', None) + user_roles = list(user_roles_qs.all()) if user_roles_qs is not None else [] + approvals_list = list(step_instance.approvals.select_related('role').all()) + approvals_by_role = {a.role_id: a for a in approvals_list} + approver_statuses = [ + { + 'role': r.role, + 'status': (approvals_by_role.get(r.role_id).decision if approvals_by_role.get(r.role_id) else None), + 'reason': (approvals_by_role.get(r.role_id).reason if approvals_by_role.get(r.role_id) else ''), + } + for r in reqs + ] + + # Accountant/Admin approval and rejection via POST (multi-role) + if request.method == 'POST' and request.POST.get('action') in ['approve', 'reject']: + # match user's role against step required approver roles + req_roles = [req.role for req in step.approver_requirements.select_related('role').all()] + user_roles = list(getattr(getattr(request.user, 'profile', None), 'roles', Role.objects.none()).all()) + matching_role = next((r for r in user_roles if r in req_roles), None) + if matching_role is None: + messages.error(request, 'شما دسترسی لازم برای تایید/رد این مرحله را ندارید.') + return redirect('invoices:quote_payment_step', instance_id=instance.id, step_id=step.id) + + action = request.POST.get('action') + if action == 'approve': + StepApproval.objects.update_or_create( + step_instance=step_instance, + role=matching_role, + defaults={'approved_by': request.user, 'decision': 'approved', 'reason': ''} + ) + if step_instance.is_fully_approved(): + step_instance.status = 'completed' + step_instance.completed_at = timezone.now() + step_instance.save() + # move to next step + redirect_url = 'processes:request_list' + if next_step: + instance.current_step = next_step + instance.save() + return redirect('processes:step_detail', instance_id=instance.id, step_id=next_step.id) + return redirect(redirect_url) + messages.success(request, 'تایید شما ثبت شد. منتظر تایید سایر نقش‌ها.') + return redirect('invoices:quote_payment_step', instance_id=instance.id, step_id=step.id) + + if action == 'reject': + reason = (request.POST.get('reject_reason') or '').strip() + if not reason: + messages.error(request, 'علت رد شدن را وارد کنید') + return redirect('invoices:quote_payment_step', instance_id=instance.id, step_id=step.id) + StepApproval.objects.update_or_create( + step_instance=step_instance, + role=matching_role, + defaults={'approved_by': request.user, 'decision': 'rejected', 'reason': reason} + ) + StepRejection.objects.create(step_instance=step_instance, rejected_by=request.user, reason=reason) + messages.success(request, 'مرحله پرداخت‌ها رد شد و برای اصلاح بازگشت.') + return redirect('invoices:quote_payment_step', instance_id=instance.id, step_id=step.id) + + # role flags for permissions (legacy flags kept for compatibility) + profile = getattr(request.user, 'profile', None) + is_broker = False + is_accountant = False + try: + is_broker = bool(profile and profile.has_role(UserRoles.BROKER)) + is_accountant = bool(profile and profile.has_role(UserRoles.ACCOUNTANT)) + except Exception: + is_broker = False + is_accountant = False return render(request, 'invoices/quote_payment_step.html', { 'instance': instance, @@ -258,6 +382,12 @@ def quote_payment_step(request, instance_id, step_id): 'totals': totals, 'previous_step': previous_step, 'next_step': next_step, + 'approver_statuses': approver_statuses, + 'is_broker': is_broker, + 'is_accountant': is_accountant, + # dynamic permissions: any role required to approve can also manage payments + 'can_manage_payments': can_approve_reject, + 'can_approve_reject': can_approve_reject, }) @@ -279,6 +409,16 @@ def add_quote_payment(request, instance_id, step_id): } ) + # dynamic permission: users whose roles are among required approvers can add payments + try: + req_role_ids = set(step.approver_requirements.values_list('role_id', flat=True)) + user_roles_qs = getattr(getattr(request.user, 'profile', None), 'roles', Role.objects.none()) + user_role_ids = set(user_roles_qs.values_list('id', flat=True)) + if len(req_role_ids.intersection(user_role_ids)) == 0: + return JsonResponse({'success': False, 'message': 'شما مجوز افزودن فیش را ندارید'}) + except Exception: + return JsonResponse({'success': False, 'message': 'شما مجوز افزودن فیش را ندارید'}) + logger = logging.getLogger(__name__) try: amount = (request.POST.get('amount') or '').strip() @@ -325,6 +465,15 @@ def add_quote_payment(request, instance_id, step_id): logger.exception('Error adding quote payment (instance=%s, step=%s)', instance_id, step_id) return JsonResponse({'success': False, 'message': 'خطا در ثبت فیش', 'error': str(e)}) + # After modifying payments, set step back to in_progress (awaiting approval) + try: + si, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step) + si.status = 'in_progress' + si.completed_at = None + si.save() + si.approvals.all().delete() + except Exception: + pass redirect_url = reverse('invoices:quote_payment_step', args=[instance.id, step.id]) return JsonResponse({'success': True, 'redirect': redirect_url}) @@ -360,6 +509,15 @@ def update_quote_payment(request, instance_id, step_id, payment_id): except Exception: return JsonResponse({'success': False, 'message': 'خطا در ویرایش فیش'}) + # On update, return to awaiting approval + try: + si, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step) + si.status = 'in_progress' + si.completed_at = None + si.save() + si.approvals.all().delete() + except Exception: + pass redirect_url = reverse('invoices:quote_payment_step', args=[instance.id, step.id]) return JsonResponse({'success': True, 'redirect': redirect_url}) @@ -374,11 +532,30 @@ def delete_quote_payment(request, instance_id, step_id, payment_id): if not invoice: return JsonResponse({'success': False, 'message': 'فاکتور یافت نشد'}) payment = get_object_or_404(Payment, id=payment_id, invoice=invoice) + # dynamic permission: users whose roles are among required approvers can delete payments + try: + req_role_ids = set(step.approver_requirements.values_list('role_id', flat=True)) + user_roles_qs = getattr(getattr(request.user, 'profile', None), 'roles', Role.objects.none()) + user_role_ids = set(user_roles_qs.values_list('id', flat=True)) + if len(req_role_ids.intersection(user_role_ids)) == 0: + return JsonResponse({'success': False, 'message': 'شما مجوز حذف فیش را ندارید'}) + except Exception: + return JsonResponse({'success': False, 'message': 'شما مجوز حذف فیش را ندارید'}) + try: # soft delete using project's BaseModel delete override payment.delete() except Exception: return JsonResponse({'success': False, 'message': 'خطا در حذف فیش'}) + # On delete, return to awaiting approval + try: + si, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step) + si.status = 'in_progress' + si.completed_at = None + si.save() + si.approvals.all().delete() + except Exception: + pass redirect_url = reverse('invoices:quote_payment_step', args=[instance.id, step.id]) return JsonResponse({'success': True, 'redirect': redirect_url}) @@ -534,6 +711,14 @@ def final_invoice_step(request, instance_id, step_id): # Choices for special items from DB special_choices = list(Item.objects.filter(is_special=True).values('id', 'name')) + # role flag for manager-only actions + profile = getattr(request.user, 'profile', None) + is_manager = False + try: + is_manager = bool(profile and profile.has_role(UserRoles.MANAGER)) + except Exception: + is_manager = False + return render(request, 'invoices/final_invoice_step.html', { 'instance': instance, 'step': step, @@ -543,6 +728,7 @@ def final_invoice_step(request, instance_id, step_id): 'invoice_specials': invoice.items.select_related('item').filter(item__is_special=True, is_deleted=False).all(), 'previous_step': previous_step, 'next_step': next_step, + 'is_manager': is_manager, }) @@ -564,6 +750,12 @@ def approve_final_invoice(request, instance_id, step_id): instance = get_object_or_404(ProcessInstance, id=instance_id) step = get_object_or_404(instance.process.steps, id=step_id) invoice = get_object_or_404(Invoice, process_instance=instance) + # only MANAGER can approve + try: + if not (hasattr(request.user, 'profile') and request.user.profile.has_role(UserRoles.MANAGER)): + return JsonResponse({'success': False, 'message': 'شما مجوز تایید این مرحله را ندارید'}, status=403) + except Exception: + return JsonResponse({'success': False, 'message': 'شما مجوز تایید این مرحله را ندارید'}, status=403) # Block approval when there is any remaining (positive or negative) invoice.calculate_totals() # if invoice.remaining_amount != 0: @@ -592,6 +784,12 @@ def add_special_charge(request, instance_id, step_id): """افزودن هزینه ویژه تعمیر/تعویض به فاکتور نهایی به‌صورت آیتم جداگانه""" instance = get_object_or_404(ProcessInstance, id=instance_id) invoice = get_object_or_404(Invoice, process_instance=instance) + # only MANAGER can add special charges + try: + if not (hasattr(request.user, 'profile') and request.user.profile.has_role(UserRoles.MANAGER)): + return JsonResponse({'success': False, 'message': 'شما مجوز افزودن هزینه ویژه را ندارید'}, status=403) + except Exception: + return JsonResponse({'success': False, 'message': 'شما مجوز افزودن هزینه ویژه را ندارید'}, status=403) # charge_type was removed from UI; we no longer require it item_id = request.POST.get('item_id') amount = (request.POST.get('amount') or '').strip() @@ -623,6 +821,12 @@ def add_special_charge(request, instance_id, step_id): def delete_special_charge(request, instance_id, step_id, item_id): instance = get_object_or_404(ProcessInstance, id=instance_id) invoice = get_object_or_404(Invoice, process_instance=instance) + # only MANAGER can delete special charges + try: + if not (hasattr(request.user, 'profile') and request.user.profile.has_role(UserRoles.MANAGER)): + return JsonResponse({'success': False, 'message': 'شما مجوز حذف هزینه ویژه را ندارید'}, status=403) + except Exception: + return JsonResponse({'success': False, 'message': 'شما مجوز حذف هزینه ویژه را ندارید'}, status=403) from .models import InvoiceItem inv_item = get_object_or_404(InvoiceItem, id=item_id, invoice=invoice) # allow deletion only for special items @@ -648,13 +852,87 @@ def final_settlement_step(request, instance_id, step_id): previous_step = instance.process.steps.filter(order__lt=step.order).last() next_step = instance.process.steps.filter(order__gt=step.order).first() + # Ensure step instance exists + step_instance, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step, defaults={'status': 'in_progress'}) + # Build approver statuses for template + reqs = list(step.approver_requirements.select_related('role').all()) + approvals_map = {a.role_id: a.decision for a in step_instance.approvals.select_related('role').all()} + approver_statuses = [{'role': r.role, 'status': approvals_map.get(r.role_id)} for r in reqs] + # dynamic permission to control approve/reject UI + try: + user_roles_qs = getattr(getattr(request.user, 'profile', None), 'roles', Role.objects.none()) + user_role_ids = set(user_roles_qs.values_list('id', flat=True)) + req_role_ids = {r.role_id for r in reqs} + can_approve_reject = len(req_role_ids.intersection(user_role_ids)) > 0 + except Exception: + can_approve_reject = False + + # Accountant/Admin approval and rejection (multi-role) + if request.method == 'POST' and request.POST.get('action') in ['approve', 'reject']: + req_roles = [req.role for req in step.approver_requirements.select_related('role').all()] + user_roles = list(getattr(getattr(request.user, 'profile', None), 'roles', Role.objects.none()).all()) + matching_role = next((r for r in user_roles if r in req_roles), None) + if matching_role is None: + messages.error(request, 'شما دسترسی لازم برای تایید/رد این مرحله را ندارید.') + return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id) + + action = request.POST.get('action') + if action == 'approve': + # enforce zero remaining + invoice.calculate_totals() + if invoice.remaining_amount != 0: + messages.error(request, f"تا زمانی که مانده فاکتور صفر نشده امکان تایید نیست (مانده فعلی: {invoice.remaining_amount})") + return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id) + StepApproval.objects.update_or_create( + step_instance=step_instance, + role=matching_role, + defaults={'approved_by': request.user, 'decision': 'approved', 'reason': ''} + ) + if step_instance.is_fully_approved(): + step_instance.status = 'completed' + step_instance.completed_at = timezone.now() + step_instance.save() + if next_step: + instance.current_step = next_step + instance.save() + return redirect('processes:step_detail', instance_id=instance.id, step_id=next_step.id) + return redirect('processes:request_list') + messages.success(request, 'تایید شما ثبت شد. منتظر تایید سایر نقش‌ها.') + return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id) + + if action == 'reject': + reason = (request.POST.get('reject_reason') or '').strip() + if not reason: + messages.error(request, 'علت رد شدن را وارد کنید') + return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id) + StepApproval.objects.update_or_create( + step_instance=step_instance, + role=matching_role, + defaults={'approved_by': request.user, 'decision': 'rejected', 'reason': reason} + ) + StepRejection.objects.create(step_instance=step_instance, rejected_by=request.user, reason=reason) + messages.success(request, 'مرحله تسویه نهایی رد شد و برای اصلاح بازگشت.') + return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id) + + # broker flag for payment management permission + profile = getattr(request.user, 'profile', None) + is_broker = False + try: + is_broker = bool(profile and profile.has_role(UserRoles.BROKER)) + except Exception: + is_broker = False + return render(request, 'invoices/final_settlement_step.html', { 'instance': instance, 'step': step, 'invoice': invoice, 'payments': invoice.payments.filter(is_deleted=False).all(), + 'step_instance': step_instance, 'previous_step': previous_step, 'next_step': next_step, + 'approver_statuses': approver_statuses, + 'can_approve_reject': can_approve_reject, + 'is_broker': is_broker, }) @@ -662,7 +940,14 @@ def final_settlement_step(request, instance_id, step_id): @login_required def add_final_payment(request, instance_id, step_id): instance = get_object_or_404(ProcessInstance, id=instance_id) + step = get_object_or_404(instance.process.steps, id=step_id) invoice = get_object_or_404(Invoice, process_instance=instance) + # Only BROKER can add final settlement payments + try: + if not (hasattr(request.user, 'profile') and request.user.profile.has_role(UserRoles.BROKER)): + return JsonResponse({'success': False, 'message': 'شما مجوز افزودن تراکنش تسویه را ندارید'}, status=403) + except Exception: + return JsonResponse({'success': False, 'message': 'شما مجوز افزودن تراکنش تسویه را ندارید'}, status=403) amount = (request.POST.get('amount') or '').strip() payment_date = (request.POST.get('payment_date') or '').strip() payment_method = (request.POST.get('payment_method') or '').strip() @@ -717,6 +1002,14 @@ def add_final_payment(request, instance_id, step_id): ) # After creation, totals auto-updated by model save. Respond with redirect and new totals for UX. invoice.refresh_from_db() + # After payment change, set step back to in_progress + try: + si, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step) + si.status = 'in_progress' + si.completed_at = None + si.save() + except Exception: + pass return JsonResponse({ 'success': True, 'redirect': reverse('invoices:final_settlement_step', args=[instance.id, step_id]), @@ -732,10 +1025,25 @@ def add_final_payment(request, instance_id, step_id): @login_required def delete_final_payment(request, instance_id, step_id, payment_id): instance = get_object_or_404(ProcessInstance, id=instance_id) + step = get_object_or_404(instance.process.steps, id=step_id) invoice = get_object_or_404(Invoice, process_instance=instance) payment = get_object_or_404(Payment, id=payment_id, invoice=invoice) + # Only BROKER can delete final settlement payments + try: + if not (hasattr(request.user, 'profile') and request.user.profile.has_role(UserRoles.BROKER)): + return JsonResponse({'success': False, 'message': 'شما مجوز حذف تراکنش تسویه را ندارید'}, status=403) + except Exception: + return JsonResponse({'success': False, 'message': 'شما مجوز حذف تراکنش تسویه را ندارید'}, status=403) payment.delete() invoice.refresh_from_db() + # After payment change, set step back to in_progress + try: + si, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step) + si.status = 'in_progress' + si.completed_at = None + si.save() + except Exception: + pass return JsonResponse({'success': True, 'redirect': reverse('invoices:final_settlement_step', args=[instance.id, step_id]), 'totals': { 'final_amount': str(invoice.final_amount), 'paid_amount': str(invoice.paid_amount), diff --git a/processes/admin.py b/processes/admin.py index 4c83c14..c8ad3ac 100644 --- a/processes/admin.py +++ b/processes/admin.py @@ -2,7 +2,7 @@ from django.contrib import admin from simple_history.admin import SimpleHistoryAdmin from django.utils.html import format_html from django.utils.safestring import mark_safe -from .models import Process, ProcessStep, ProcessInstance, StepInstance, StepDependency, StepRejection, StepRevision +from .models import Process, ProcessStep, ProcessInstance, StepInstance, StepDependency, StepRejection, StepRevision, StepApproverRequirement, StepApproval @admin.register(Process) class ProcessAdmin(SimpleHistoryAdmin): @@ -179,3 +179,17 @@ class StepRevisionAdmin(SimpleHistoryAdmin): def changes_short(self, obj): return obj.changes_description[:50] + "..." if len(obj.changes_description) > 50 else obj.changes_description changes_short.short_description = "تغییرات" + + +@admin.register(StepApproverRequirement) +class StepApproverRequirementAdmin(admin.ModelAdmin): + list_display = ("step", "role", "required_count") + list_filter = ("step__process", "role") + search_fields = ("step__name", "role__name") + + +@admin.register(StepApproval) +class StepApprovalAdmin(admin.ModelAdmin): + list_display = ("step_instance", "role", "decision", "approved_by", "created_at") + list_filter = ("decision", "role", "step_instance__step__process") + search_fields = ("step_instance__process_instance__code", "role__name", "approved_by__username") diff --git a/processes/migrations/0002_stepapproval_stepapproverrequirement.py b/processes/migrations/0002_stepapproval_stepapproverrequirement.py new file mode 100644 index 0000000..6a771b1 --- /dev/null +++ b/processes/migrations/0002_stepapproval_stepapproverrequirement.py @@ -0,0 +1,48 @@ +# Generated by Django 5.2.4 on 2025-09-01 10:33 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0003_historicalprofile_bank_name_profile_bank_name'), + ('processes', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='StepApproval', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('decision', models.CharField(choices=[('approved', 'تایید'), ('rejected', 'رد')], max_length=8, verbose_name='نتیجه')), + ('reason', models.TextField(blank=True, verbose_name='علت (برای رد)')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='تاریخ')), + ('approved_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='تاییدکننده')), + ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.role', verbose_name='نقش')), + ('step_instance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='approvals', to='processes.stepinstance', verbose_name='نمونه مرحله')), + ], + options={ + 'verbose_name': 'تایید مرحله', + 'verbose_name_plural': 'تاییدهای مرحله', + 'unique_together': {('step_instance', 'role')}, + }, + ), + migrations.CreateModel( + name='StepApproverRequirement', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('required_count', models.PositiveIntegerField(default=1, verbose_name='تعداد موردنیاز')), + ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.role', verbose_name='نقش تاییدکننده')), + ('step', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='approver_requirements', to='processes.processstep', verbose_name='مرحله')), + ], + options={ + 'verbose_name': 'نیازمندی تایید نقش', + 'verbose_name_plural': 'نیازمندی\u200cهای تایید نقش', + 'unique_together': {('step', 'role')}, + }, + ), + ] diff --git a/processes/models.py b/processes/models.py index cfbdfa7..604bb7b 100644 --- a/processes/models.py +++ b/processes/models.py @@ -4,6 +4,8 @@ from common.models import NameSlugModel, SluggedModel from simple_history.models import HistoricalRecords from django.core.exceptions import ValidationError from django.utils import timezone +from django.conf import settings +from accounts.models import Role from _helpers.utils import generate_unique_slug import random @@ -46,6 +48,9 @@ class ProcessStep(NameSlugModel): ) history = HistoricalRecords() + # Note: approver requirements are defined via StepApproverRequirement through model + # See StepApproverRequirement below + class Meta: verbose_name = "مرحله فرآیند" verbose_name_plural = "مراحل فرآیند" @@ -353,6 +358,26 @@ class StepInstance(models.Model): """دریافت آخرین رد شدن""" return self.rejections.order_by('-created_at').first() + # -------- Multi-role approval helpers -------- + def required_roles(self): + return [req.role for req in self.step.approver_requirements.select_related('role').all()] + + def approvals_by_role(self): + decisions = {} + for a in self.approvals.select_related('role').order_by('created_at'): + decisions[a.role_id] = a.decision + return decisions + + def is_fully_approved(self) -> bool: + req_roles = self.required_roles() + if not req_roles: + return True + role_to_decision = self.approvals_by_role() + for r in req_roles: + if role_to_decision.get(r.id) != 'approved': + return False + return True + class StepRejection(models.Model): """مدل رد شدن مرحله""" step_instance = models.ForeignKey( @@ -424,3 +449,36 @@ class StepRevision(models.Model): def __str__(self): return f"بازبینی {self.step_instance} توسط {self.revised_by.get_full_name()}" + + +class StepApproverRequirement(models.Model): + """Required approver roles for a step.""" + step = models.ForeignKey(ProcessStep, on_delete=models.CASCADE, related_name='approver_requirements', verbose_name="مرحله") + role = models.ForeignKey(Role, on_delete=models.CASCADE, verbose_name="نقش تاییدکننده") + required_count = models.PositiveIntegerField(default=1, verbose_name="تعداد موردنیاز") + + class Meta: + unique_together = ('step', 'role') + verbose_name = "نیازمندی تایید نقش" + verbose_name_plural = "نیازمندی‌های تایید نقش" + + def __str__(self): + return f"{self.step} ← {self.role} (x{self.required_count})" + + +class StepApproval(models.Model): + """Approvals per role for a concrete step instance.""" + step_instance = models.ForeignKey(StepInstance, on_delete=models.CASCADE, related_name='approvals', verbose_name="نمونه مرحله") + role = models.ForeignKey(Role, on_delete=models.CASCADE, verbose_name="نقش") + approved_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name="تاییدکننده") + decision = models.CharField(max_length=8, choices=[('approved', 'تایید'), ('rejected', 'رد')], verbose_name='نتیجه') + reason = models.TextField(blank=True, verbose_name='علت (برای رد)') + created_at = models.DateTimeField(auto_now_add=True, verbose_name='تاریخ') + + class Meta: + unique_together = ('step_instance', 'role') + verbose_name = 'تایید مرحله' + verbose_name_plural = 'تاییدهای مرحله' + + def __str__(self): + return f"{self.step_instance} - {self.role} - {self.decision}" diff --git a/processes/templates/processes/includes/stepper_header.html b/processes/templates/processes/includes/stepper_header.html index bd30927..505ecd8 100644 --- a/processes/templates/processes/includes/stepper_header.html +++ b/processes/templates/processes/includes/stepper_header.html @@ -6,6 +6,7 @@
{% endif %} - {{ forloop.counter }} + {{ forloop.counter }} - {{ step.name }} + {{ step.name }} {{ step.description|default:' ' }} diff --git a/processes/templates/processes/instance_summary.html b/processes/templates/processes/instance_summary.html new file mode 100644 index 0000000..cb5171e --- /dev/null +++ b/processes/templates/processes/instance_summary.html @@ -0,0 +1,168 @@ + {% extends '_base.html' %} +{% load static %} +{% load humanize %} +{% load common_tags %} + +{% block sidebar %} + {% include 'sidebars/admin.html' %} +{% endblock sidebar %} + +{% block navbar %} + {% include 'navbars/admin.html' %} +{% endblock navbar %} + +{% block title %}گزارش نهایی - درخواست {{ instance.code }}{% endblock %} + +{% block content %} +{% include '_toasts.html' %} +
+
+
+
+
+

گزارش نهایی درخواست {{ instance.code }}

+ + اشتراک آب: {{ instance.well.water_subscription_number|default:"-" }} + | نماینده: {{ instance.representative.profile.national_code|default:"-" }} + +
+
+ {% if invoice %} + پرینت فاکتور + {% endif %} + پرینت گواهی + بازگشت +
+
+ +
+
+
+
+
فاکتور نهایی
+
+
+ {% if invoice %} +
+
مبلغ نهایی
{{ invoice.final_amount|floatformat:0|intcomma:False }} تومان
+
پرداختی‌ها
{{ invoice.paid_amount|floatformat:0|intcomma:False }} تومان
+
مانده
{{ invoice.remaining_amount|floatformat:0|intcomma:False }} تومان
+
+
+ + + + + + + + + + + {% for it in rows %} + + + + + + + {% empty %} + + {% endfor %} + +
آیتمتعدادقیمت واحدقیمت کل
{{ it.item.name }}{{ it.quantity }}{{ it.unit_price|floatformat:0|intcomma:False }}{{ it.total_price|floatformat:0|intcomma:False }}
اطلاعاتی ندارد
+
+ {% else %} +
فاکتور نهایی ثبت نشده است.
+ {% endif %} +
+
+
+ +
+
+
+
گزارش نصب
+ {% if latest_report and latest_report.assignment and latest_report.assignment.installer %} + نصاب: {{ latest_report.assignment.installer.get_full_name|default:latest_report.assignment.installer.username }} + {% endif %} +
+
+ {% if latest_report %} +
+
+

تاریخ مراجعه: {{ latest_report.visited_date|to_jalali|default:'-' }}

+

سریال کنتور جدید: {{ latest_report.new_water_meter_serial|default:'-' }}

+

شماره پلمپ: {{ latest_report.seal_number|default:'-' }}

+
+
+

کنتور مشکوک: {{ latest_report.is_meter_suspicious|yesno:'بله,خیر' }}

+

UTM X: {{ latest_report.utm_x|default:'-' }}

+

UTM Y: {{ latest_report.utm_y|default:'-' }}

+
+
+ {% if latest_report.description %} +
+

توضیحات:

+
{{ latest_report.description }}
+
+ {% endif %} +
+
عکس‌ها
+
+ {% for p in latest_report.photos.all %} +
photo
+ {% empty %} +
بدون عکس
+ {% endfor %} +
+ {% else %} +
گزارش نصب ثبت نشده است.
+ {% endif %} +
+
+
+ +
+
+
+
تراکنش‌ها
+
+
+
+ + + + + + + + + + + + {% for p in payments %} + + + + + + + + {% empty %} + + {% endfor %} + +
نوعمبلغتاریخروششماره مرجع/چک
{% if p.direction == 'in' %}دریافتی{% else %}پرداختی{% endif %}{{ p.amount|floatformat:0|intcomma:False }} تومان{{ p.payment_date|date:'Y/m/d' }}{{ p.get_payment_method_display }}{{ p.reference_number|default:'-' }}
بدون تراکنش
+
+
+
+
+ +
+
+
+
+{% endblock %} + + diff --git a/processes/templates/processes/request_list.html b/processes/templates/processes/request_list.html index acf274c..6ca6d95 100644 --- a/processes/templates/processes/request_list.html +++ b/processes/templates/processes/request_list.html @@ -72,9 +72,15 @@