complete first version of main proccess
This commit is contained in:
		
							parent
							
								
									6ff4740d04
								
							
						
					
					
						commit
						f2fc2362a7
					
				
					 61 changed files with 3280 additions and 28 deletions
				
			
		
							
								
								
									
										255
									
								
								installations/views.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								installations/views.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,255 @@
 | 
			
		|||
from django.shortcuts import render, get_object_or_404, redirect
 | 
			
		||||
from django.contrib.auth.decorators import login_required
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
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 invoices.models import Item, Quote, QuoteItem
 | 
			
		||||
from .models import InstallationAssignment, InstallationReport, InstallationPhoto, InstallationItemChange
 | 
			
		||||
from decimal import Decimal, InvalidOperation
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def installation_assign_step(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)
 | 
			
		||||
    previous_step = instance.process.steps.filter(order__lt=step.order).last()
 | 
			
		||||
    next_step = instance.process.steps.filter(order__gt=step.order).first()
 | 
			
		||||
 | 
			
		||||
    # Installers list (profiles that have installer role)
 | 
			
		||||
    installers = Profile.objects.filter(roles__slug=UserRoles.INSTALLER.value).select_related('user').all()
 | 
			
		||||
    assignment, _ = InstallationAssignment.objects.get_or_create(process_instance=instance)
 | 
			
		||||
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        installer_id = request.POST.get('installer_id')
 | 
			
		||||
        scheduled_date = (request.POST.get('scheduled_date') or '').strip()
 | 
			
		||||
        assignment.installer_id = installer_id or None
 | 
			
		||||
        if scheduled_date:
 | 
			
		||||
            assignment.scheduled_date = scheduled_date.replace('/', '-')
 | 
			
		||||
        assignment.assigned_by = request.user
 | 
			
		||||
        assignment.save()
 | 
			
		||||
 | 
			
		||||
        # complete step
 | 
			
		||||
        StepInstance.objects.update_or_create(
 | 
			
		||||
            process_instance=instance,
 | 
			
		||||
            step=step,
 | 
			
		||||
            defaults={'status': 'completed', 'completed_at': timezone.now()}
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        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')
 | 
			
		||||
 | 
			
		||||
    return render(request, 'installations/installation_assign_step.html', {
 | 
			
		||||
        'instance': instance,
 | 
			
		||||
        'step': step,
 | 
			
		||||
        'assignment': assignment,
 | 
			
		||||
        'installers': installers,
 | 
			
		||||
        'previous_step': previous_step,
 | 
			
		||||
        'next_step': next_step,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def installation_report_step(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)
 | 
			
		||||
    previous_step = instance.process.steps.filter(order__lt=step.order).last()
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
    # 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')
 | 
			
		||||
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
        # 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.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()
 | 
			
		||||
            for item_id, qty in remove_map.items():
 | 
			
		||||
                up = quote_price_map.get(item_id)
 | 
			
		||||
                total = (up * qty) if up is not None else None
 | 
			
		||||
                InstallationItemChange.objects.create(
 | 
			
		||||
                    report=report,
 | 
			
		||||
                    item_id=item_id,
 | 
			
		||||
                    change_type='remove',
 | 
			
		||||
                    quantity=qty,
 | 
			
		||||
                    unit_price=up,
 | 
			
		||||
                    total_price=total,
 | 
			
		||||
                )
 | 
			
		||||
            for item_id, data in add_map.items():
 | 
			
		||||
                unit_price = data.get('price')
 | 
			
		||||
                qty = data.get('qty') or 1
 | 
			
		||||
                total = (unit_price * qty) if (unit_price is not None) else None
 | 
			
		||||
                InstallationItemChange.objects.create(
 | 
			
		||||
                    report=report,
 | 
			
		||||
                    item_id=item_id,
 | 
			
		||||
                    change_type='add',
 | 
			
		||||
                    quantity=qty,
 | 
			
		||||
                    unit_price=unit_price,
 | 
			
		||||
                    total_price=total,
 | 
			
		||||
                )
 | 
			
		||||
        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
 | 
			
		||||
            for item_id, qty in remove_map.items():
 | 
			
		||||
                up = quote_price_map.get(item_id)
 | 
			
		||||
                total = (up * qty) if up is not None else None
 | 
			
		||||
                InstallationItemChange.objects.create(
 | 
			
		||||
                    report=report,
 | 
			
		||||
                    item_id=item_id,
 | 
			
		||||
                    change_type='remove',
 | 
			
		||||
                    quantity=qty,
 | 
			
		||||
                    unit_price=up,
 | 
			
		||||
                    total_price=total,
 | 
			
		||||
                )
 | 
			
		||||
            for item_id, data in add_map.items():
 | 
			
		||||
                unit_price = data.get('price')
 | 
			
		||||
                qty = data.get('qty') or 1
 | 
			
		||||
                total = (unit_price * qty) if (unit_price is not None) else None
 | 
			
		||||
                InstallationItemChange.objects.create(
 | 
			
		||||
                    report=report,
 | 
			
		||||
                    item_id=item_id,
 | 
			
		||||
                    change_type='add',
 | 
			
		||||
                    quantity=qty,
 | 
			
		||||
                    unit_price=unit_price,
 | 
			
		||||
                    total_price=total,
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
        # complete step
 | 
			
		||||
        StepInstance.objects.update_or_create(
 | 
			
		||||
            process_instance=instance,
 | 
			
		||||
            step=step,
 | 
			
		||||
            defaults={'status': 'completed', 'completed_at': timezone.now()}
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        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')
 | 
			
		||||
 | 
			
		||||
    # Build prefill maps from existing report changes
 | 
			
		||||
    removed_ids = set()
 | 
			
		||||
    removed_qty = {}
 | 
			
		||||
    added_map = {}
 | 
			
		||||
    if existing_report:
 | 
			
		||||
        for ch in existing_report.item_changes.all():
 | 
			
		||||
            if ch.change_type == 'remove':
 | 
			
		||||
                removed_ids.add(ch.item_id)
 | 
			
		||||
                removed_qty[ch.item_id] = ch.quantity
 | 
			
		||||
            elif ch.change_type == 'add':
 | 
			
		||||
                added_map[ch.item_id] = {'qty': ch.quantity, 'price': ch.unit_price}
 | 
			
		||||
 | 
			
		||||
    return render(request, 'installations/installation_report_step.html', {
 | 
			
		||||
        'instance': instance,
 | 
			
		||||
        'step': step,
 | 
			
		||||
        'assignment': assignment,
 | 
			
		||||
        'report': existing_report,
 | 
			
		||||
        'edit_mode': edit_mode,
 | 
			
		||||
        'quote': quote,
 | 
			
		||||
        'quote_items': quote_items,
 | 
			
		||||
        'all_items': items,
 | 
			
		||||
        'removed_ids': removed_ids,
 | 
			
		||||
        'removed_qty': removed_qty,
 | 
			
		||||
        'added_map': added_map,
 | 
			
		||||
        'previous_step': previous_step,
 | 
			
		||||
        'next_step': next_step,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue