huge fix
This commit is contained in:
		
							parent
							
								
									810c87e2e0
								
							
						
					
					
						commit
						b5bf3a5dbe
					
				
					 51 changed files with 2397 additions and 326 deletions
				
			
		| 
						 | 
				
			
			@ -3,12 +3,15 @@ from django.contrib.auth.decorators import login_required
 | 
			
		|||
from django.contrib import messages
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.core.exceptions import ValidationError
 | 
			
		||||
from accounts.models import Profile
 | 
			
		||||
from common.consts import UserRoles
 | 
			
		||||
from processes.models import ProcessInstance, StepInstance, StepRejection, StepApproval
 | 
			
		||||
from accounts.models import Role
 | 
			
		||||
from invoices.models import Item, Quote, QuoteItem
 | 
			
		||||
from wells.models import WaterMeterManufacturer
 | 
			
		||||
from .models import InstallationAssignment, InstallationReport, InstallationPhoto, InstallationItemChange
 | 
			
		||||
from .forms import InstallationReportForm
 | 
			
		||||
from decimal import Decimal, InvalidOperation
 | 
			
		||||
from processes.utils import get_scoped_instance_or_404
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -122,12 +125,9 @@ def installation_report_step(request, instance_id, step_id):
 | 
			
		|||
    is_assigned_installer = bool(assignment and assignment.installer_id == request.user.id)
 | 
			
		||||
    user_is_installer = bool(has_installer_role and is_assigned_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.filter(is_active=True, is_special=False, is_deleted=False).order_by('name')
 | 
			
		||||
    # Prevent edit mode if an approved report exists
 | 
			
		||||
    if existing_report and existing_report.approved:
 | 
			
		||||
        edit_mode = False
 | 
			
		||||
 | 
			
		||||
    # Ensure a StepInstance exists for this step
 | 
			
		||||
    step_instance, _ = StepInstance.objects.get_or_create(
 | 
			
		||||
| 
						 | 
				
			
			@ -136,6 +136,177 @@ def installation_report_step(request, instance_id, step_id):
 | 
			
		|||
        defaults={'status': 'in_progress'}
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # 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.filter(is_active=True, is_special=False, is_deleted=False).order_by('name')
 | 
			
		||||
    manufacturers = WaterMeterManufacturer.objects.all().order_by('name')
 | 
			
		||||
    
 | 
			
		||||
    # Initialize the form
 | 
			
		||||
    form = None
 | 
			
		||||
    if request.method == 'POST' and request.POST.get('action') not in ['approve', 'reject']:
 | 
			
		||||
        # Handle form submission for report creation/editing
 | 
			
		||||
        if not user_is_installer:
 | 
			
		||||
            messages.error(request, 'شما مجوز ثبت/ویرایش گزارش نصب را ندارید')
 | 
			
		||||
            return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id)
 | 
			
		||||
        # Block editing approved reports
 | 
			
		||||
        if existing_report and existing_report.approved:
 | 
			
		||||
            messages.error(request, 'این گزارش قبلا تایید شده و قابل ویرایش نیست')
 | 
			
		||||
            return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id)
 | 
			
		||||
            
 | 
			
		||||
        form = InstallationReportForm(
 | 
			
		||||
            request.POST, 
 | 
			
		||||
            instance=existing_report if edit_mode else None,
 | 
			
		||||
            user_is_installer=user_is_installer,
 | 
			
		||||
            instance_well=instance.well
 | 
			
		||||
        )
 | 
			
		||||
        
 | 
			
		||||
        if form.is_valid():
 | 
			
		||||
            # Validate photos
 | 
			
		||||
            photo_validation_passed = False
 | 
			
		||||
            try:
 | 
			
		||||
                deleted_photo_ids = []
 | 
			
		||||
                for key, val in request.POST.items():
 | 
			
		||||
                    if key.startswith('del_photo_') and val == '1':
 | 
			
		||||
                        try:
 | 
			
		||||
                            pid = key.split('_')[-1]
 | 
			
		||||
                            deleted_photo_ids.append(pid)
 | 
			
		||||
                        except Exception:
 | 
			
		||||
                            continue
 | 
			
		||||
                
 | 
			
		||||
                existing_photos = existing_report.photos.all() if existing_report else None
 | 
			
		||||
                form.validate_photos(request.FILES, existing_photos, deleted_photo_ids)
 | 
			
		||||
                photo_validation_passed = True
 | 
			
		||||
            except ValidationError as e:
 | 
			
		||||
                form.add_error(None, str(e))
 | 
			
		||||
                # Re-render form with photo validation error
 | 
			
		||||
                photo_validation_passed = False
 | 
			
		||||
            
 | 
			
		||||
            # Always clear approvals/rejections when form is submitted (even if photo validation fails)
 | 
			
		||||
            # Reset step status and clear approvals/rejections
 | 
			
		||||
            step_instance.status = 'in_progress'
 | 
			
		||||
            step_instance.completed_at = None
 | 
			
		||||
            step_instance.save()
 | 
			
		||||
            try:
 | 
			
		||||
                for appr in list(step_instance.approvals.all()):
 | 
			
		||||
                    appr.delete()
 | 
			
		||||
            except Exception:
 | 
			
		||||
                pass
 | 
			
		||||
            try:
 | 
			
		||||
                for rej in list(step_instance.rejections.all()):
 | 
			
		||||
                    rej.delete()
 | 
			
		||||
            except Exception:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            # Reopen subsequent steps
 | 
			
		||||
            try:
 | 
			
		||||
                subsequent_steps = instance.process.steps.filter(order__gt=step.order)
 | 
			
		||||
                for subsequent_step in subsequent_steps:
 | 
			
		||||
                    subsequent_step_instance = instance.step_instances.filter(step=subsequent_step).first()
 | 
			
		||||
                    if subsequent_step_instance and subsequent_step_instance.status == 'completed':
 | 
			
		||||
                        instance.step_instances.filter(step=subsequent_step).update(
 | 
			
		||||
                            status='in_progress',
 | 
			
		||||
                            completed_at=None
 | 
			
		||||
                        )
 | 
			
		||||
                        try:
 | 
			
		||||
                            for appr in list(subsequent_step_instance.approvals.all()):
 | 
			
		||||
                                appr.delete()
 | 
			
		||||
                        except Exception:
 | 
			
		||||
                            pass
 | 
			
		||||
            except Exception:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            # Reset current step if needed
 | 
			
		||||
            try:
 | 
			
		||||
                if instance.current_step and instance.current_step.order > step.order:
 | 
			
		||||
                    instance.current_step = step
 | 
			
		||||
                    instance.save(update_fields=['current_step'])
 | 
			
		||||
            except Exception:
 | 
			
		||||
                pass
 | 
			
		||||
            
 | 
			
		||||
            # Only save the report if photo validation passed
 | 
			
		||||
            if photo_validation_passed:
 | 
			
		||||
                # Save the form
 | 
			
		||||
                report = form.save(commit=False)
 | 
			
		||||
                if not existing_report:
 | 
			
		||||
                    report.assignment = assignment
 | 
			
		||||
                    report.created_by = request.user
 | 
			
		||||
                report.approved = False  # Reset approval status
 | 
			
		||||
                report.save()
 | 
			
		||||
                
 | 
			
		||||
                # Handle photo uploads and deletions
 | 
			
		||||
                if existing_report and edit_mode:
 | 
			
		||||
                    # Delete selected existing photos
 | 
			
		||||
                    for key, val in request.POST.items():
 | 
			
		||||
                        if key.startswith('del_photo_') and val == '1':
 | 
			
		||||
                            try:
 | 
			
		||||
                                pid = int(key.split('_')[-1])
 | 
			
		||||
                                InstallationPhoto.objects.filter(id=pid, report=report).delete()
 | 
			
		||||
                            except Exception:
 | 
			
		||||
                                continue
 | 
			
		||||
                
 | 
			
		||||
                # Add new photos
 | 
			
		||||
                for f in request.FILES.getlist('photos'):
 | 
			
		||||
                    InstallationPhoto.objects.create(report=report, image=f)
 | 
			
		||||
                
 | 
			
		||||
                # Handle item changes (this logic remains the same)
 | 
			
		||||
                remove_map = {}
 | 
			
		||||
                add_map = {}
 | 
			
		||||
                for key in request.POST.keys():
 | 
			
		||||
                    if key.startswith('rem_') and key.endswith('_type'):
 | 
			
		||||
                        try:
 | 
			
		||||
                            item_id = int(key.split('_')[1])
 | 
			
		||||
                        except Exception:
 | 
			
		||||
                            continue
 | 
			
		||||
                        if request.POST.get(key) != 'remove':
 | 
			
		||||
                            continue
 | 
			
		||||
                        qty_val = request.POST.get(f'rem_{item_id}_qty') or '1'
 | 
			
		||||
                        try:
 | 
			
		||||
                            qty = int(qty_val)
 | 
			
		||||
                        except Exception:
 | 
			
		||||
                            qty = 1
 | 
			
		||||
                        remove_map[item_id] = qty
 | 
			
		||||
                    if key.startswith('add_') and key.endswith('_type'):
 | 
			
		||||
                        try:
 | 
			
		||||
                            item_id = int(key.split('_')[1])
 | 
			
		||||
                        except Exception:
 | 
			
		||||
                            continue
 | 
			
		||||
                        if request.POST.get(key) != 'add':
 | 
			
		||||
                            continue
 | 
			
		||||
                        qty_val = request.POST.get(f'add_{item_id}_qty') or '1'
 | 
			
		||||
                        price_val = request.POST.get(f'add_{item_id}_price')
 | 
			
		||||
                        try:
 | 
			
		||||
                            qty = int(qty_val)
 | 
			
		||||
                        except Exception:
 | 
			
		||||
                            qty = 1
 | 
			
		||||
                        # resolve unit price
 | 
			
		||||
                        unit_price = None
 | 
			
		||||
                        if price_val:
 | 
			
		||||
                            try:
 | 
			
		||||
                                unit_price = Decimal(price_val)
 | 
			
		||||
                            except InvalidOperation:
 | 
			
		||||
                                unit_price = None
 | 
			
		||||
                        if unit_price is None:
 | 
			
		||||
                            item_obj = Item.objects.filter(id=item_id).first()
 | 
			
		||||
                            unit_price = item_obj.unit_price if item_obj else None
 | 
			
		||||
                        add_map[item_id] = {'qty': qty, 'price': unit_price}
 | 
			
		||||
                
 | 
			
		||||
                # Replace item changes with new submission
 | 
			
		||||
                if existing_report and edit_mode:
 | 
			
		||||
                    report.item_changes.all().delete()
 | 
			
		||||
                create_item_changes_for_report(report, remove_map, add_map, quote_price_map)
 | 
			
		||||
 | 
			
		||||
                messages.success(request, 'گزارش ثبت شد و در انتظار تایید است.')
 | 
			
		||||
                return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id)
 | 
			
		||||
    else:
 | 
			
		||||
        # GET request or approval/rejection actions - initialize form for display
 | 
			
		||||
        form = InstallationReportForm(
 | 
			
		||||
            instance=existing_report if existing_report else None,
 | 
			
		||||
            user_is_installer=user_is_installer,
 | 
			
		||||
            instance_well=instance.well
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    # 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)
 | 
			
		||||
| 
						 | 
				
			
			@ -148,7 +319,7 @@ def installation_report_step(request, instance_id, step_id):
 | 
			
		|||
    except Exception:
 | 
			
		||||
        can_approve_reject = False
 | 
			
		||||
    user_can_approve = can_approve_reject
 | 
			
		||||
    approvals_list = list(step_instance.approvals.select_related('role').all())
 | 
			
		||||
    approvals_list = list(step_instance.approvals.select_related('role', 'approved_by').filter(is_deleted=False))
 | 
			
		||||
    approvals_by_role = {a.role_id: a for a in approvals_list}
 | 
			
		||||
    approver_statuses = [
 | 
			
		||||
        {
 | 
			
		||||
| 
						 | 
				
			
			@ -159,6 +330,15 @@ def installation_report_step(request, instance_id, step_id):
 | 
			
		|||
        for r in reqs
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    # Determine if current user has already approved/rejected (to disable buttons)
 | 
			
		||||
    current_user_has_decided = False
 | 
			
		||||
    try:
 | 
			
		||||
        user_has_approval = step_instance.approvals.filter(approved_by=request.user, is_deleted=False).exists()
 | 
			
		||||
        user_has_rejection = step_instance.rejections.filter(rejected_by=request.user, is_deleted=False).exists()
 | 
			
		||||
        current_user_has_decided = bool(user_has_approval or user_has_rejection)
 | 
			
		||||
    except Exception:
 | 
			
		||||
        current_user_has_decided = False
 | 
			
		||||
 | 
			
		||||
    # Manager approval/rejection actions
 | 
			
		||||
    if request.method == 'POST' and request.POST.get('action') in ['approve', 'reject']:
 | 
			
		||||
        action = request.POST.get('action')
 | 
			
		||||
| 
						 | 
				
			
			@ -175,14 +355,16 @@ def installation_report_step(request, instance_id, step_id):
 | 
			
		|||
            return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id)
 | 
			
		||||
 | 
			
		||||
        if action == 'approve':
 | 
			
		||||
            existing_report.approved = True
 | 
			
		||||
            existing_report.save()
 | 
			
		||||
            # Record this user's approval for their role
 | 
			
		||||
            StepApproval.objects.update_or_create(
 | 
			
		||||
                step_instance=step_instance,
 | 
			
		||||
                role=matching_role,
 | 
			
		||||
                defaults={'approved_by': request.user, 'decision': 'approved', 'reason': ''}
 | 
			
		||||
            )
 | 
			
		||||
            # Only mark report approved when ALL required roles have approved
 | 
			
		||||
            if step_instance.is_fully_approved():
 | 
			
		||||
                existing_report.approved = True
 | 
			
		||||
                existing_report.save()
 | 
			
		||||
                step_instance.status = 'completed'
 | 
			
		||||
                step_instance.completed_at = timezone.now()
 | 
			
		||||
                step_instance.save()
 | 
			
		||||
| 
						 | 
				
			
			@ -191,6 +373,11 @@ def installation_report_step(request, instance_id, step_id):
 | 
			
		|||
                    instance.save()
 | 
			
		||||
                    return redirect('processes:step_detail', instance_id=instance.id, step_id=next_step.id)
 | 
			
		||||
                return redirect('processes:request_list')
 | 
			
		||||
            else:
 | 
			
		||||
                # Not fully approved yet; keep report as not approved
 | 
			
		||||
                if existing_report.approved:
 | 
			
		||||
                    existing_report.approved = False
 | 
			
		||||
                    existing_report.save(update_fields=['approved'])
 | 
			
		||||
            messages.success(request, 'تایید شما ثبت شد. منتظر تایید سایر نقشها.')
 | 
			
		||||
            return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -217,160 +404,6 @@ def installation_report_step(request, instance_id, step_id):
 | 
			
		|||
            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:
 | 
			
		||||
            visited_date = visited_date.replace('/', '-')
 | 
			
		||||
        new_serial = (request.POST.get('new_water_meter_serial') or '').strip()
 | 
			
		||||
        seal_number = (request.POST.get('seal_number') or '').strip()
 | 
			
		||||
        is_suspicious = True if request.POST.get('is_meter_suspicious') == 'on' else False
 | 
			
		||||
        utm_x = request.POST.get('utm_x') or None
 | 
			
		||||
        utm_y = request.POST.get('utm_y') or None
 | 
			
		||||
        # Normalize UTM to integer meters
 | 
			
		||||
        if utm_x is not None and utm_x != '':
 | 
			
		||||
            try:
 | 
			
		||||
                utm_x = int(Decimal(str(utm_x)))
 | 
			
		||||
            except InvalidOperation:
 | 
			
		||||
                utm_x = None
 | 
			
		||||
        else:
 | 
			
		||||
            utm_x = None
 | 
			
		||||
        if utm_y is not None and utm_y != '':
 | 
			
		||||
            try:
 | 
			
		||||
                utm_y = int(Decimal(str(utm_y)))
 | 
			
		||||
            except InvalidOperation:
 | 
			
		||||
                utm_y = None
 | 
			
		||||
        else:
 | 
			
		||||
            utm_y = None
 | 
			
		||||
 | 
			
		||||
        # Build maps from form fields: remove and add
 | 
			
		||||
        remove_map = {}
 | 
			
		||||
        add_map = {}
 | 
			
		||||
        for key in request.POST.keys():
 | 
			
		||||
            if key.startswith('rem_') and key.endswith('_type'):
 | 
			
		||||
                # rem_{id}_type = 'remove'
 | 
			
		||||
                try:
 | 
			
		||||
                    item_id = int(key.split('_')[1])
 | 
			
		||||
                except Exception:
 | 
			
		||||
                    continue
 | 
			
		||||
                if request.POST.get(key) != 'remove':
 | 
			
		||||
                    continue
 | 
			
		||||
                qty_val = request.POST.get(f'rem_{item_id}_qty') or '1'
 | 
			
		||||
                try:
 | 
			
		||||
                    qty = int(qty_val)
 | 
			
		||||
                except Exception:
 | 
			
		||||
                    qty = 1
 | 
			
		||||
                remove_map[item_id] = qty
 | 
			
		||||
            if key.startswith('add_') and key.endswith('_type'):
 | 
			
		||||
                try:
 | 
			
		||||
                    item_id = int(key.split('_')[1])
 | 
			
		||||
                except Exception:
 | 
			
		||||
                    continue
 | 
			
		||||
                if request.POST.get(key) != 'add':
 | 
			
		||||
                    continue
 | 
			
		||||
                qty_val = request.POST.get(f'add_{item_id}_qty') or '1'
 | 
			
		||||
                price_val = request.POST.get(f'add_{item_id}_price')
 | 
			
		||||
                try:
 | 
			
		||||
                    qty = int(qty_val)
 | 
			
		||||
                except Exception:
 | 
			
		||||
                    qty = 1
 | 
			
		||||
                # resolve unit price
 | 
			
		||||
                unit_price = None
 | 
			
		||||
                if price_val:
 | 
			
		||||
                    try:
 | 
			
		||||
                        unit_price = Decimal(price_val)
 | 
			
		||||
                    except InvalidOperation:
 | 
			
		||||
                        unit_price = None
 | 
			
		||||
                if unit_price is None:
 | 
			
		||||
                    item_obj = Item.objects.filter(id=item_id).first()
 | 
			
		||||
                    unit_price = item_obj.unit_price if item_obj else None
 | 
			
		||||
                add_map[item_id] = {'qty': qty, 'price': unit_price}
 | 
			
		||||
 | 
			
		||||
        if existing_report and edit_mode:
 | 
			
		||||
            report = existing_report
 | 
			
		||||
            report.description = description
 | 
			
		||||
            report.visited_date = visited_date or None
 | 
			
		||||
            report.new_water_meter_serial = new_serial or None
 | 
			
		||||
            report.seal_number = seal_number or None
 | 
			
		||||
            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():
 | 
			
		||||
                if key.startswith('del_photo_') and val == '1':
 | 
			
		||||
                    try:
 | 
			
		||||
                        pid = int(key.split('_')[-1])
 | 
			
		||||
                        InstallationPhoto.objects.filter(id=pid, report=report).delete()
 | 
			
		||||
                    except Exception:
 | 
			
		||||
                        continue
 | 
			
		||||
            # append new photos
 | 
			
		||||
            for f in request.FILES.getlist('photos'):
 | 
			
		||||
                InstallationPhoto.objects.create(report=report, image=f)
 | 
			
		||||
            # replace item changes with new submission
 | 
			
		||||
            report.item_changes.all().delete()
 | 
			
		||||
            create_item_changes_for_report(report, remove_map, add_map, quote_price_map)
 | 
			
		||||
        else:
 | 
			
		||||
            report = InstallationReport.objects.create(
 | 
			
		||||
                assignment=assignment,
 | 
			
		||||
                description=description,
 | 
			
		||||
                visited_date=visited_date or None,
 | 
			
		||||
                new_water_meter_serial=new_serial or None,
 | 
			
		||||
                seal_number=seal_number or None,
 | 
			
		||||
                is_meter_suspicious=is_suspicious,
 | 
			
		||||
                utm_x=utm_x,
 | 
			
		||||
                utm_y=utm_y,
 | 
			
		||||
                created_by=request.user,
 | 
			
		||||
            )
 | 
			
		||||
            # photos
 | 
			
		||||
            for f in request.FILES.getlist('photos'):
 | 
			
		||||
                InstallationPhoto.objects.create(report=report, image=f)
 | 
			
		||||
            # item changes
 | 
			
		||||
            create_item_changes_for_report(report, remove_map, add_map, quote_price_map)
 | 
			
		||||
 | 
			
		||||
        # 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 the report was edited, ensure downstream steps reopen like invoices flow
 | 
			
		||||
        try:
 | 
			
		||||
            subsequent_steps = instance.process.steps.filter(order__gt=step.order)
 | 
			
		||||
            for subsequent_step in subsequent_steps:
 | 
			
		||||
                subsequent_step_instance = instance.step_instances.filter(step=subsequent_step).first()
 | 
			
		||||
                if subsequent_step_instance and subsequent_step_instance.status == 'completed':
 | 
			
		||||
                    # Reopen the step
 | 
			
		||||
                    instance.step_instances.filter(step=subsequent_step).update(
 | 
			
		||||
                        status='in_progress',
 | 
			
		||||
                        completed_at=None
 | 
			
		||||
                    )
 | 
			
		||||
                    # Clear previous approvals if any
 | 
			
		||||
                    try:
 | 
			
		||||
                        subsequent_step_instance.approvals.all().delete()
 | 
			
		||||
                    except Exception:
 | 
			
		||||
                        pass
 | 
			
		||||
        except Exception:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        # If current step is ahead of this step, reset it back to this step
 | 
			
		||||
        try:
 | 
			
		||||
            if instance.current_step and instance.current_step.order > step.order:
 | 
			
		||||
                instance.current_step = step
 | 
			
		||||
                instance.save(update_fields=['current_step'])
 | 
			
		||||
        except Exception:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        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()
 | 
			
		||||
| 
						 | 
				
			
			@ -389,11 +422,13 @@ def installation_report_step(request, instance_id, step_id):
 | 
			
		|||
        'step': step,
 | 
			
		||||
        'assignment': assignment,
 | 
			
		||||
        'report': existing_report,
 | 
			
		||||
        'form': form,
 | 
			
		||||
        'edit_mode': edit_mode,
 | 
			
		||||
        'user_is_installer': user_is_installer,
 | 
			
		||||
        'quote': quote,
 | 
			
		||||
        'quote_items': quote_items,
 | 
			
		||||
        'all_items': items,
 | 
			
		||||
        'manufacturers': manufacturers,
 | 
			
		||||
        'removed_ids': removed_ids,
 | 
			
		||||
        'removed_qty': removed_qty,
 | 
			
		||||
        'added_map': added_map,
 | 
			
		||||
| 
						 | 
				
			
			@ -403,6 +438,7 @@ def installation_report_step(request, instance_id, step_id):
 | 
			
		|||
        'approver_statuses': approver_statuses,
 | 
			
		||||
        'user_can_approve': user_can_approve,
 | 
			
		||||
        'can_approve_reject': can_approve_reject,
 | 
			
		||||
        'current_user_has_decided': current_user_has_decided,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue