Add confirmation and summary
This commit is contained in:
parent
9b3973805e
commit
35799b7754
25 changed files with 1419 additions and 265 deletions
|
@ -1,6 +1,7 @@
|
|||
{% extends '_base.html' %}
|
||||
{% load static %}
|
||||
{% load processes_tags %}
|
||||
{% load common_tags %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block sidebar %}
|
||||
|
@ -41,12 +42,15 @@
|
|||
|
||||
<div class="bs-stepper-content">
|
||||
|
||||
{% if show_denied_msg %}
|
||||
<div class="alert alert-warning mb-3">شما اجازه تعیین نصاب را ندارید.</div>
|
||||
{% endif %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">نصاب</label>
|
||||
<select name="installer_id" class="form-select" required>
|
||||
<select name="installer_id" class="form-select" {% if read_only %}disabled{% endif %} required>
|
||||
<option value="">انتخاب کنید...</option>
|
||||
{% for p in installers %}
|
||||
<option value="{{ p.user.id }}" {% if assignment.installer and p.user.id == assignment.installer.id %}selected{% endif %}>{{ p.user.get_full_name }} ({{ p.user.username }})</option>
|
||||
|
@ -55,17 +59,39 @@
|
|||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">تاریخ مراجعه نصاب</label>
|
||||
<input type="text" id="id_scheduled_date_display" class="form-control" placeholder="انتخاب تاریخ" readonly required value="{% if assignment.scheduled_date %}{{ assignment.scheduled_date|date:'Y/m/d' }}{% endif %}">
|
||||
<input type="text" id="id_scheduled_date_display" class="form-control" placeholder="انتخاب تاریخ" {% if read_only %}disabled{% endif %} readonly required value="{% if assignment.scheduled_date %}{{ assignment.scheduled_date|date:'Y/m/d' }}{% endif %}">
|
||||
<input type="hidden" id="id_scheduled_date" name="scheduled_date" value="{% if assignment.scheduled_date %}{{ assignment.scheduled_date|date:'Y-m-d' }}{% endif %}">
|
||||
</div>
|
||||
</div>
|
||||
{% if assignment.assigned_by or assignment.installer %}
|
||||
<div class="mt-3 border rounded p-3 bg-light">
|
||||
<div class="row g-2">
|
||||
{% if assignment.assigned_by %}
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="small text-muted">تعیینکننده نصاب</div>
|
||||
<div>{{ assignment.assigned_by.get_full_name|default:assignment.assigned_by.username }} <span class="text-muted">({{ assignment.assigned_by.username }})</span></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if assignment.updated %}
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="small text-muted">تاریخ ثبت/ویرایش</div>
|
||||
<div>{{ assignment.updated|to_jalali }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
{% if previous_step %}
|
||||
<a href="{% url 'processes:step_detail' instance.id previous_step.id %}" class="btn btn-label-secondary">قبلی</a>
|
||||
{% else %}
|
||||
<span></span>
|
||||
{% endif %}
|
||||
<button class="btn btn-primary" type="submit">ثبت و ادامه</button>
|
||||
{% if is_manager %}
|
||||
<button class="btn btn-primary" type="submit">ثبت و ادامه</button>
|
||||
{% else %}
|
||||
<a href="{% url 'processes:step_detail' instance.id next_step.id %}" class="btn btn-primary">بعدی</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -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 %}
|
||||
|
||||
<div class="bs-stepper-content">
|
||||
|
||||
{% if report and not edit_mode %}
|
||||
<div class="card mb-3 border">
|
||||
<div class="card-header d-flex justify-content-end">
|
||||
<a href="?edit=1" class="btn btn-primary">ویرایش گزارش نصب</a>
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex gap-2">
|
||||
{% if request.user|is_installer %}
|
||||
<a href="?edit=1" class="btn btn-primary">ویرایش گزارش نصب</a>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-primary" disabled>ویرایش گزارش نصب</button>
|
||||
{% endif %}
|
||||
{% if user_can_approve %}
|
||||
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#approveModal" {% if step_instance and step_instance.status == 'completed' %}disabled{% endif %}>تایید</button>
|
||||
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#rejectModal">رد</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if step_instance and step_instance.status == 'rejected' and step_instance.get_latest_rejection %}
|
||||
<div class="alert alert-danger d-flex align-items-start" role="alert">
|
||||
<i class="bx bx-error-circle me-2"></i>
|
||||
<div>
|
||||
<div><strong>این گزارش رد شده است.</strong></div>
|
||||
<div class="mt-1 small">علت رد: {{ step_instance.get_latest_rejection.reason }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="text-nowrap mb-2"><i class="bx bx-calendar-event bx-sm me-2"></i>تاریخ مراجعه: {{ report.visited_date|to_jalali|default:'-' }}</p>
|
||||
|
@ -67,6 +86,9 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
<hr>
|
||||
{% if request.user|is_manager or request.user|is_admin %}
|
||||
<hr>
|
||||
{% endif %}
|
||||
<h6>عکسها</h6>
|
||||
<div class="row">
|
||||
{% for p in report.photos.all %}
|
||||
|
@ -115,6 +137,42 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if approver_statuses %}
|
||||
<div class="card border mt-2">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">وضعیت تاییدها</h6>
|
||||
{% if user_can_approve %}
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#approveModal" {% if step_instance and step_instance.status == 'completed' %}disabled{% endif %}>تایید</button>
|
||||
<button type="button" class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#rejectModal">رد</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body py-3">
|
||||
<div class="row g-2">
|
||||
{% for st in approver_statuses %}
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
<div class="d-flex flex-column border rounded px-2 py-1">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="badge bg-light text-dark">{{ st.role.name }}</span>
|
||||
{% if st.status == 'approved' %}
|
||||
<span class="badge bg-success">تایید شد</span>
|
||||
{% elif st.status == 'rejected' %}
|
||||
<span class="badge bg-danger">رد شد</span>
|
||||
{% else %}
|
||||
<span class="badge bg-warning text-dark">در انتظار</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if st.status == 'rejected' and st.reason %}
|
||||
<div class="mt-1 small text-danger">علت: {{ st.reason }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- Persistent nav in edit mode (outside cards) -->
|
||||
<div class="d-flex justify-content-between mt-3">
|
||||
{% if previous_step %}
|
||||
|
@ -127,6 +185,9 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
{% if not request.user|is_installer %}
|
||||
<div class="alert alert-warning">شما مجوز ثبت/ویرایش گزارش نصب را ندارید. اطلاعات به صورت فقط خواندنی نمایش داده میشود.</div>
|
||||
{% endif %}
|
||||
<form method="post" enctype="multipart/form-data" id="installation-report-form">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
|
@ -134,40 +195,42 @@
|
|||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">تاریخ مراجعه</label>
|
||||
<input type="text" id="id_visited_date_display" class="form-control" placeholder="انتخاب تاریخ" readonly required value="{% if report and edit_mode and report.visited_date %}{{ report.visited_date|date:'Y/m/d' }}{% endif %}">
|
||||
<input type="text" id="id_visited_date_display" class="form-control" placeholder="انتخاب تاریخ" {% if not request.user|is_installer %}disabled{% endif %} readonly required value="{% if report and edit_mode and report.visited_date %}{{ report.visited_date|date:'Y/m/d' }}{% endif %}">
|
||||
<input type="hidden" id="id_visited_date" name="visited_date" value="{% if report and edit_mode and report.visited_date %}{{ report.visited_date|date:'Y-m-d' }}{% endif %}">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">سریال کنتور جدید</label>
|
||||
<input type="text" class="form-control" name="new_water_meter_serial">
|
||||
<input type="text" class="form-control" name="new_water_meter_serial" value="{% if report and edit_mode %}{{ report.new_water_meter_serial|default_if_none:'' }}{% endif %}" {% if not request.user|is_installer %}readonly{% endif %}>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">شماره پلمپ</label>
|
||||
<input type="text" class="form-control" name="seal_number">
|
||||
<input type="text" class="form-control" name="seal_number" value="{% if report and edit_mode %}{{ report.seal_number|default_if_none:'' }}{% endif %}" {% if not request.user|is_installer %}readonly{% endif %}>
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="is_meter_suspicious" id="id_is_meter_suspicious">
|
||||
<input class="form-check-input" type="checkbox" name="is_meter_suspicious" id="id_is_meter_suspicious" {% if not request.user|is_installer %}disabled{% endif %} {% if report and edit_mode and report.is_meter_suspicious %}checked{% endif %}>
|
||||
<label class="form-check-label" for="id_is_meter_suspicious">کنتور مشکوک است</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">UTM X</label>
|
||||
<input type="number" step="0.000001" class="form-control" name="utm_x" value="{% if instance.well.utm_x %}{{ instance.well.utm_x }}{% endif %}">
|
||||
<input type="number" step="0.000001" class="form-control" name="utm_x" value="{% if report and edit_mode and report.utm_x %}{{ report.utm_x }}{% elif instance.well.utm_x %}{{ instance.well.utm_x }}{% endif %}" {% if not request.user|is_installer %}readonly{% endif %}>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">UTM Y</label>
|
||||
<input type="number" step="0.000001" class="form-control" name="utm_y" value="{% if instance.well.utm_y %}{{ instance.well.utm_y }}{% endif %}">
|
||||
<input type="number" step="0.000001" class="form-control" name="utm_y" value="{% if report and edit_mode and report.utm_y %}{{ report.utm_y }}{% elif instance.well.utm_y %}{{ instance.well.utm_y }}{% endif %}" {% if not request.user|is_installer %}readonly{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<label class="form-label">توضیحات (اختیاری)</label>
|
||||
<textarea class="form-control" rows="3" name="description"></textarea>
|
||||
<textarea class="form-control" rows="3" name="description" {% if not request.user|is_installer %}readonly{% endif %}>{% if report and edit_mode %}{{ report.description|default_if_none:'' }}{% endif %}</textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<label class="form-label mb-0">عکسها</label>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" id="btnAddPhoto"><i class="bx bx-plus"></i> افزودن عکس</button>
|
||||
{% if request.user|is_installer %}
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" id="btnAddPhoto"><i class="bx bx-plus"></i> افزودن عکس</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if report %}
|
||||
<div class="row mt-2">
|
||||
|
@ -175,7 +238,9 @@
|
|||
<div class="col-6 col-md-3 mb-2" id="existing-photo-{{ p.id }}">
|
||||
<div class="position-relative border rounded p-1">
|
||||
<img class="img-fluid rounded" src="{{ p.image.url }}" alt="photo">
|
||||
<button type="button" class="btn btn-sm btn-danger position-absolute" style="top:6px; left:6px;" onclick="markDeletePhoto({{ p.id }})" title="حذف/برگردان"><i class='bx bx-trash'></i></button>
|
||||
{% if request.user|is_installer %}
|
||||
<button type="button" class="btn btn-sm btn-danger position-absolute" style="top:6px; left:6px;" onclick="markDeletePhoto('{{ p.id }}')" title="حذف/برگردان"><i class="bx bx-trash"></i></button>
|
||||
{% endif %}
|
||||
<input type="hidden" name="del_photo_{{ p.id }}" id="del-photo-{{ p.id }}" value="0">
|
||||
</div>
|
||||
</div>
|
||||
|
@ -285,7 +350,11 @@
|
|||
<span></span>
|
||||
{% endif %}
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary" form="installation-report-form">ثبت گزارش</button>
|
||||
{% if request.user|is_installer %}
|
||||
<button type="submit" class="btn btn-primary" form="installation-report-form">ثبت گزارش</button>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-primary" disabled>ثبت گزارش</button>
|
||||
{% endif %}
|
||||
{% if next_step %}
|
||||
<a href="{% url 'processes:step_detail' instance.id next_step.id %}" class="btn btn-success">بعدی</a>
|
||||
{% endif %}
|
||||
|
@ -298,6 +367,58 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Approve Modal -->
|
||||
<div class="modal fade" id="approveModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="approve">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">تایید گزارش نصب</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
آیا از تایید این گزارش اطمینان دارید؟
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-label-secondary" data-bs-dismiss="modal">انصراف</button>
|
||||
<button type="submit" class="btn btn-success">تایید</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reject Modal -->
|
||||
<div class="modal fade" id="rejectModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="reject">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">رد گزارش نصب</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-2">
|
||||
<label class="form-label">علت رد</label>
|
||||
<textarea class="form-control" name="reject_reason" rows="3" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-label-secondary" data-bs-dismiss="modal">انصراف</button>
|
||||
<button type="submit" class="btn btn-danger">ثبت رد</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
|
@ -445,4 +566,3 @@
|
|||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue