huge fix
This commit is contained in:
		
							parent
							
								
									810c87e2e0
								
							
						
					
					
						commit
						b5bf3a5dbe
					
				
					 51 changed files with 2397 additions and 326 deletions
				
			
		| 
						 | 
				
			
			@ -5,6 +5,7 @@ from django.contrib import messages
 | 
			
		|||
from django.http import JsonResponse
 | 
			
		||||
from django.views.decorators.http import require_POST
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from decimal import Decimal, InvalidOperation
 | 
			
		||||
import json
 | 
			
		||||
| 
						 | 
				
			
			@ -356,16 +357,16 @@ def quote_payment_step(request, instance_id, step_id):
 | 
			
		|||
    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_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 = [
 | 
			
		||||
        {
 | 
			
		||||
    approver_statuses = []
 | 
			
		||||
    for r in reqs:
 | 
			
		||||
        appr = approvals_by_role.get(r.role_id)
 | 
			
		||||
        approver_statuses.append({
 | 
			
		||||
            '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
 | 
			
		||||
    ]
 | 
			
		||||
            'status': (appr.decision if appr else None),
 | 
			
		||||
            'reason': (appr.reason if appr else ''),
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    # dynamic permission: who can approve/reject this step (based on requirements)
 | 
			
		||||
    try:
 | 
			
		||||
| 
						 | 
				
			
			@ -374,6 +375,15 @@ def quote_payment_step(request, instance_id, step_id):
 | 
			
		|||
        can_approve_reject = len(req_role_ids.intersection(user_role_ids)) > 0
 | 
			
		||||
    except Exception:
 | 
			
		||||
        can_approve_reject = False
 | 
			
		||||
 | 
			
		||||
    # Compute whether current user has already decided (approved/rejected)
 | 
			
		||||
    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
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    # Accountant/Admin approval and rejection via POST (multi-role)
 | 
			
		||||
| 
						 | 
				
			
			@ -452,6 +462,7 @@ def quote_payment_step(request, instance_id, step_id):
 | 
			
		|||
        'is_broker': is_broker,
 | 
			
		||||
        'is_accountant': is_accountant,
 | 
			
		||||
        'can_approve_reject': can_approve_reject,
 | 
			
		||||
        'current_user_has_decided': current_user_has_decided,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -537,7 +548,17 @@ def add_quote_payment(request, instance_id, step_id):
 | 
			
		|||
        si.status = 'in_progress'
 | 
			
		||||
        si.completed_at = None
 | 
			
		||||
        si.save()
 | 
			
		||||
        si.approvals.all().delete()
 | 
			
		||||
    except Exception:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        for appr in list(si.approvals.all()):
 | 
			
		||||
            appr.delete()
 | 
			
		||||
    except Exception:
 | 
			
		||||
        pass
 | 
			
		||||
    try:
 | 
			
		||||
        for rej in list(si.rejections.all()):
 | 
			
		||||
            rej.delete()
 | 
			
		||||
    except Exception:
 | 
			
		||||
        pass
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			@ -554,7 +575,8 @@ def add_quote_payment(request, instance_id, step_id):
 | 
			
		|||
                )
 | 
			
		||||
                # Clear previous approvals if the step requires re-approval
 | 
			
		||||
                try:
 | 
			
		||||
                    subsequent_step_instance.approvals.all().delete()
 | 
			
		||||
                    for appr in list(subsequent_step_instance.approvals.all()):
 | 
			
		||||
                        appr.delete()
 | 
			
		||||
                except Exception:
 | 
			
		||||
                    pass
 | 
			
		||||
    except Exception:
 | 
			
		||||
| 
						 | 
				
			
			@ -596,7 +618,7 @@ def delete_quote_payment(request, instance_id, step_id, payment_id):
 | 
			
		|||
 | 
			
		||||
    try:
 | 
			
		||||
        # soft delete using project's BaseModel delete override
 | 
			
		||||
        payment.delete()
 | 
			
		||||
        payment.hard_delete()
 | 
			
		||||
    except Exception:
 | 
			
		||||
        return JsonResponse({'success': False, 'message': 'خطا در حذف فیش'})
 | 
			
		||||
    # On delete, return to awaiting approval
 | 
			
		||||
| 
						 | 
				
			
			@ -605,7 +627,10 @@ def delete_quote_payment(request, instance_id, step_id, payment_id):
 | 
			
		|||
        si.status = 'in_progress'
 | 
			
		||||
        si.completed_at = None
 | 
			
		||||
        si.save()
 | 
			
		||||
        si.approvals.all().delete()
 | 
			
		||||
        for appr in list(si.approvals.all()):
 | 
			
		||||
            appr.delete()
 | 
			
		||||
        for rej in list(si.rejections.all()):
 | 
			
		||||
            rej.delete()
 | 
			
		||||
    except Exception:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -622,7 +647,8 @@ def delete_quote_payment(request, instance_id, step_id, payment_id):
 | 
			
		|||
                )
 | 
			
		||||
                # Clear previous approvals if the step requires re-approval
 | 
			
		||||
                try:
 | 
			
		||||
                    subsequent_step_instance.approvals.all().delete()
 | 
			
		||||
                    for appr in list(subsequent_step_instance.approvals.all()):
 | 
			
		||||
                        appr.delete()
 | 
			
		||||
                except Exception:
 | 
			
		||||
                    pass
 | 
			
		||||
    except Exception:
 | 
			
		||||
| 
						 | 
				
			
			@ -707,16 +733,15 @@ def final_invoice_step(request, instance_id, step_id):
 | 
			
		|||
                if ch.unit_price:
 | 
			
		||||
                    row['base_price'] = _to_decimal(ch.unit_price)
 | 
			
		||||
 | 
			
		||||
    # Compute final invoice lines
 | 
			
		||||
    # Compute final invoice lines (include fully removed items for display)
 | 
			
		||||
    rows = []
 | 
			
		||||
    total_amount = Decimal('0')
 | 
			
		||||
    for _, r in item_id_to_row.items():
 | 
			
		||||
        final_qty = max(0, (r['base_qty'] + r['added_qty'] - r['removed_qty']))
 | 
			
		||||
        if final_qty == 0:
 | 
			
		||||
            continue
 | 
			
		||||
        unit_price_dec = _to_decimal(r['base_price'])
 | 
			
		||||
        line_total = Decimal(final_qty) * unit_price_dec
 | 
			
		||||
        total_amount += line_total
 | 
			
		||||
        line_total = Decimal(final_qty) * unit_price_dec if final_qty > 0 else Decimal('0')
 | 
			
		||||
        if final_qty > 0:
 | 
			
		||||
            total_amount += line_total
 | 
			
		||||
        rows.append({
 | 
			
		||||
            'item': r['item'],
 | 
			
		||||
            'quantity': final_qty,
 | 
			
		||||
| 
						 | 
				
			
			@ -725,6 +750,7 @@ def final_invoice_step(request, instance_id, step_id):
 | 
			
		|||
            'base_qty': r['base_qty'],
 | 
			
		||||
            'added_qty': r['added_qty'],
 | 
			
		||||
            'removed_qty': r['removed_qty'],
 | 
			
		||||
            'is_removed': True if final_qty == 0 else False,
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    # Create or reuse final invoice
 | 
			
		||||
| 
						 | 
				
			
			@ -745,6 +771,8 @@ def final_invoice_step(request, instance_id, step_id):
 | 
			
		|||
    except Exception:
 | 
			
		||||
        qs.delete()
 | 
			
		||||
    for r in rows:
 | 
			
		||||
        if r['quantity'] <= 0:
 | 
			
		||||
            continue
 | 
			
		||||
        from .models import InvoiceItem
 | 
			
		||||
        InvoiceItem.objects.create(
 | 
			
		||||
            invoice=invoice,
 | 
			
		||||
| 
						 | 
				
			
			@ -918,12 +946,21 @@ def final_settlement_step(request, instance_id, step_id):
 | 
			
		|||
    except Exception:
 | 
			
		||||
        can_approve_reject = False
 | 
			
		||||
 | 
			
		||||
    # Compute whether current user has already decided (approved/rejected)
 | 
			
		||||
    current_user_has_decided = False
 | 
			
		||||
    try:
 | 
			
		||||
        user_has_approval = step_instance.approvals.filter(approved_by=request.user).exists()
 | 
			
		||||
        user_has_rejection = step_instance.rejections.filter(rejected_by=request.user).exists()
 | 
			
		||||
        current_user_has_decided = bool(user_has_approval or user_has_rejection)
 | 
			
		||||
    except Exception:
 | 
			
		||||
        current_user_has_decided = False
 | 
			
		||||
 | 
			
		||||
    # Accountant/Admin approval and rejection (multi-role)
 | 
			
		||||
    if request.method == 'POST' and request.POST.get('action') in ['approve', 'reject']:
 | 
			
		||||
    if request.method == 'POST' and request.POST.get('action') in ['approve', 'reject', 'force_approve']:
 | 
			
		||||
        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:
 | 
			
		||||
        if matching_role is None and request.POST.get('action') != 'force_approve':
 | 
			
		||||
            messages.error(request, 'شما دسترسی لازم برای تایید/رد این مرحله را ندارید.')
 | 
			
		||||
            return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -972,6 +1009,24 @@ def final_settlement_step(request, instance_id, step_id):
 | 
			
		|||
            messages.success(request, 'مرحله تسویه نهایی رد شد و برای اصلاح بازگشت.')
 | 
			
		||||
            return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id)
 | 
			
		||||
 | 
			
		||||
        if action == 'force_approve':
 | 
			
		||||
            # Only MANAGER can force approve
 | 
			
		||||
            try:
 | 
			
		||||
                if not (hasattr(request.user, 'profile') and request.user.profile.has_role(UserRoles.MANAGER)):
 | 
			
		||||
                    messages.error(request, 'فقط مدیر مجاز به تایید اضطراری است.')
 | 
			
		||||
                    return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id)
 | 
			
		||||
            except Exception:
 | 
			
		||||
                messages.error(request, 'فقط مدیر مجاز به تایید اضطراری است.')
 | 
			
		||||
                return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id)
 | 
			
		||||
            # Mark step completed regardless of remaining amount/approvals
 | 
			
		||||
            step_instance.status = 'approved'
 | 
			
		||||
            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')
 | 
			
		||||
 | 
			
		||||
    # broker flag for payment management permission
 | 
			
		||||
    profile = getattr(request.user, 'profile', None)
 | 
			
		||||
    is_broker = False
 | 
			
		||||
| 
						 | 
				
			
			@ -991,6 +1046,8 @@ def final_settlement_step(request, instance_id, step_id):
 | 
			
		|||
        'approver_statuses': approver_statuses,
 | 
			
		||||
        'can_approve_reject': can_approve_reject,
 | 
			
		||||
        'is_broker': is_broker,
 | 
			
		||||
        'current_user_has_decided': current_user_has_decided,
 | 
			
		||||
        'is_manager': bool(getattr(getattr(request.user, 'profile', None), 'roles', Role.objects.none()).filter(slug=UserRoles.MANAGER.value).exists()) if getattr(request.user, 'profile', None) else False,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1065,10 +1122,20 @@ def add_final_payment(request, instance_id, step_id):
 | 
			
		|||
    # On delete, return to awaiting approval
 | 
			
		||||
    try:
 | 
			
		||||
        si, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step)
 | 
			
		||||
        si.status = 'in_progress'
 | 
			
		||||
        if si.status != 'approved':
 | 
			
		||||
            si.status = 'in_progress'
 | 
			
		||||
        si.completed_at = None
 | 
			
		||||
        si.save()
 | 
			
		||||
        si.approvals.all().delete()
 | 
			
		||||
        try:
 | 
			
		||||
            for appr in list(si.approvals.all()):
 | 
			
		||||
                appr.delete()
 | 
			
		||||
        except Exception:
 | 
			
		||||
            pass
 | 
			
		||||
        try:
 | 
			
		||||
            for rej in list(si.rejections.all()):
 | 
			
		||||
                rej.delete()
 | 
			
		||||
        except Exception:
 | 
			
		||||
            pass
 | 
			
		||||
    except Exception:
 | 
			
		||||
        pass
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			@ -1085,7 +1152,8 @@ def add_final_payment(request, instance_id, step_id):
 | 
			
		|||
                )
 | 
			
		||||
                # Clear previous approvals if the step requires re-approval
 | 
			
		||||
                try:
 | 
			
		||||
                    subsequent_step_instance.approvals.all().delete()
 | 
			
		||||
                    for appr in list(subsequent_step_instance.approvals.all()):
 | 
			
		||||
                        appr.delete()
 | 
			
		||||
                except Exception:
 | 
			
		||||
                    pass
 | 
			
		||||
    except Exception:
 | 
			
		||||
| 
						 | 
				
			
			@ -1124,7 +1192,7 @@ def delete_final_payment(request, instance_id, step_id, payment_id):
 | 
			
		|||
            return JsonResponse({'success': False, 'message': 'شما مجوز حذف تراکنش تسویه را ندارید'}, status=403)
 | 
			
		||||
    except Exception:
 | 
			
		||||
        return JsonResponse({'success': False, 'message': 'شما مجوز حذف تراکنش تسویه را ندارید'}, status=403)
 | 
			
		||||
    payment.delete()
 | 
			
		||||
    payment.hard_delete()
 | 
			
		||||
    invoice.refresh_from_db()
 | 
			
		||||
 | 
			
		||||
    # On delete, return to awaiting approval
 | 
			
		||||
| 
						 | 
				
			
			@ -1133,7 +1201,16 @@ def delete_final_payment(request, instance_id, step_id, payment_id):
 | 
			
		|||
        si.status = 'in_progress'
 | 
			
		||||
        si.completed_at = None
 | 
			
		||||
        si.save()
 | 
			
		||||
        si.approvals.all().delete()
 | 
			
		||||
        try:
 | 
			
		||||
            for appr in list(si.approvals.all()):
 | 
			
		||||
                appr.delete()
 | 
			
		||||
        except Exception:
 | 
			
		||||
            pass
 | 
			
		||||
        try:
 | 
			
		||||
            for rej in list(si.rejections.all()):
 | 
			
		||||
                rej.delete()
 | 
			
		||||
        except Exception:
 | 
			
		||||
            pass
 | 
			
		||||
    except Exception:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1150,7 +1227,8 @@ def delete_final_payment(request, instance_id, step_id, payment_id):
 | 
			
		|||
                )
 | 
			
		||||
                # Clear previous approvals if the step requires re-approval
 | 
			
		||||
                try:
 | 
			
		||||
                    subsequent_step_instance.approvals.all().delete()
 | 
			
		||||
                    for appr in list(subsequent_step_instance.approvals.all()):
 | 
			
		||||
                        appr.delete()
 | 
			
		||||
                except Exception:
 | 
			
		||||
                    pass
 | 
			
		||||
    except Exception:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue