-
-
فاکتور نهایی
-
کد درخواست: {{ instance.code }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
لوگو
+
+
+
+
+
اطلاعات مشترک
+
نام: {{ invoice.customer.get_full_name|default:instance.representative.get_full_name }}
+ {% if instance.representative.profile and instance.representative.profile.national_code %}
+
کد ملی: {{ instance.representative.profile.national_code }}
+ {% endif %}
+ {% if instance.representative.profile and instance.representative.profile.phone_number_1 %}
+
تلفن: {{ instance.representative.profile.phone_number_1 }}
+ {% endif %}
+ {% if instance.representative.profile and instance.representative.profile.address %}
+
آدرس: {{ instance.representative.profile.address }}
+ {% endif %}
+
+
+
اطلاعات چاه
+
شماره اشتراک آب: {{ instance.well.water_subscription_number }}
+
شماره اشتراک برق: {{ instance.well.electricity_subscription_number|default:"-" }}
+
سریال کنتور: {{ instance.well.water_meter_serial_number|default:"-" }}
+
قدرت چاه: {{ instance.well.well_power|default:"-" }}
+
-
-
-
-
-
- آیتم |
- تعداد |
- قیمت واحد |
- قیمت کل |
-
-
-
- {% for it in items %}
-
- {{ it.item.name }} |
- {{ it.quantity }} |
- {{ it.unit_price|floatformat:0|intcomma:False }} |
- {{ it.total_price|floatformat:0|intcomma:False }} |
-
- {% empty %}
- آیتمی ندارد |
- {% endfor %}
-
-
- مبلغ کل | {{ invoice.total_amount|floatformat:0|intcomma:False }} |
- تخفیف | {{ invoice.discount_amount|floatformat:0|intcomma:False }} |
- مبلغ نهایی | {{ invoice.final_amount|floatformat:0|intcomma:False }} |
- پرداختیها | {{ invoice.paid_amount|floatformat:0|intcomma:False }} |
- مانده | {{ invoice.remaining_amount|floatformat:0|intcomma:False }} |
-
-
-
-
-
امضا مشتری
-
امضا شرکت
-
-
-
-{% endblock %}
+
+
+
+
+
+ ردیف |
+ شرح کالا/خدمات |
+ توضیحات |
+ تعداد |
+ قیمت واحد(تومان) |
+ قیمت کل(تومان) |
+
+
+
+ {% for it in items %}
+
+ {{ forloop.counter }} |
+ {{ it.item.name }} |
+ {{ it.item.description|default:"-" }} |
+ {{ it.quantity }} |
+ {{ it.unit_price|floatformat:0|intcomma:False }} |
+ {{ it.total_price|floatformat:0|intcomma:False }} |
+
+ {% empty %}
+ آیتمی ندارد |
+ {% endfor %}
+
+
+
+ جمع کل(تومان): |
+ {{ invoice.total_amount|floatformat:0|intcomma:False }} |
+
+ {% if invoice.discount_amount > 0 %}
+
+ تخفیف(تومان): |
+ {{ invoice.discount_amount|floatformat:0|intcomma:False }} |
+
+ {% endif %}
+
+ مبلغ نهایی(تومان): |
+ {{ invoice.final_amount|floatformat:0|intcomma:False }} |
+
+
+ پرداختیها(تومان): |
+ {{ invoice.paid_amount|floatformat:0|intcomma:False }} |
+
+
+ مانده(تومان): |
+ {{ invoice.remaining_amount|floatformat:0|intcomma:False }} |
+
+
+
+
+
+
+
+
مهر و امضا:
+
+ {% if instance.broker.company and instance.broker.company.signature %}
+ 
+ {% endif %}
+
+
+ {% if instance.broker.company %}
+
+
اطلاعات پرداخت
+ {% if instance.broker.company.card_number %}
+
شماره کارت: {{ instance.broker.company.card_number }}
+ {% endif %}
+ {% if instance.broker.company.account_number %}
+
شماره حساب: {{ instance.broker.company.account_number }}
+ {% endif %}
+ {% if instance.broker.company.sheba_number %}
+
شماره شبا: {{ instance.broker.company.sheba_number }}
+ {% endif %}
+ {% if instance.broker.company.bank_name %}
+
بانک: {{ instance.broker.company.get_bank_name_display }}
+ {% endif %}
+
+ {% endif %}
+
+
+
+
+
+
+
diff --git a/invoices/templates/invoices/final_invoice_step.html b/invoices/templates/invoices/final_invoice_step.html
index dfee339..5fe05e4 100644
--- a/invoices/templates/invoices/final_invoice_step.html
+++ b/invoices/templates/invoices/final_invoice_step.html
@@ -24,6 +24,10 @@
{% block content %}
{% include '_toasts.html' %}
+
+
+{% instance_info_modal instance %}
+
{% csrf_token %}
@@ -32,14 +36,18 @@
{{ step.name }}: {{ instance.process.name }}
- اشتراک آب: {{ instance.well.water_subscription_number|default:"-" }}
- | نماینده: {{ instance.representative.profile.national_code|default:"-" }}
+ {% instance_info instance %}
@@ -163,15 +171,24 @@
diff --git a/invoices/templates/invoices/final_settlement_step.html b/invoices/templates/invoices/final_settlement_step.html
index 2335298..350d5b7 100644
--- a/invoices/templates/invoices/final_settlement_step.html
+++ b/invoices/templates/invoices/final_settlement_step.html
@@ -23,6 +23,10 @@
{% block content %}
{% include '_toasts.html' %}
+
+
+{% instance_info_modal instance %}
+
{% csrf_token %}
@@ -31,14 +35,18 @@
{{ step.name }}: {{ instance.process.name }}
- اشتراک آب: {{ instance.well.water_subscription_number|default:"-" }}
- | نماینده: {{ instance.representative.profile.national_code|default:"-" }}
+ {% instance_info instance %}
@@ -88,7 +96,7 @@
-
+
{% if previous_step %}
-
قبلی
+
+
+ قبلی
+
{% else %}
{% endif %}
{% if step_instance.status == 'completed' %}
{% if next_step %}
-
بعدی
+
+ بعدی
+
+
{% else %}
اتمام
{% endif %}
diff --git a/invoices/urls.py b/invoices/urls.py
index f959799..f40df30 100644
--- a/invoices/urls.py
+++ b/invoices/urls.py
@@ -31,5 +31,4 @@ urlpatterns = [
path('instance/
/step//final-settlement/', views.final_settlement_step, name='final_settlement_step'),
path('instance//step//final-settlement/add/', views.add_final_payment, name='add_final_payment'),
path('instance//step//final-settlement//delete/', views.delete_final_payment, name='delete_final_payment'),
- path('instance//step//final-settlement/approve/', views.approve_final_settlement, name='approve_final_settlement'),
]
diff --git a/invoices/views.py b/invoices/views.py
index ea99eb7..b8a1eb2 100644
--- a/invoices/views.py
+++ b/invoices/views.py
@@ -12,7 +12,7 @@ import json
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 .models import Item, Quote, QuoteItem, Payment, Invoice, InvoiceItem
from installations.models import InstallationReport, InstallationItemChange
@@ -792,14 +792,7 @@ def approve_final_invoice(request, instance_id, step_id):
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:
- # return JsonResponse({
- # 'success': False,
- # 'message': f"تا زمانی که مانده فاکتور صفر نشده امکان تایید نیست (مانده فعلی: {invoice.remaining_amount})"
- # })
- # mark step completed
+
step_instance, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step)
step_instance.status = 'completed'
step_instance.completed_at = timezone.now()
@@ -826,7 +819,7 @@ def add_special_charge(request, instance_id, step_id):
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()
if not item_id:
@@ -841,7 +834,7 @@ def add_special_charge(request, instance_id, step_id):
# Fetch existing special item from DB
special_item = get_object_or_404(Item, id=item_id, is_special=True)
- from .models import InvoiceItem
+
InvoiceItem.objects.create(
invoice=invoice,
item=special_item,
@@ -863,7 +856,6 @@ def delete_special_charge(request, instance_id, step_id, item_id):
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
try:
@@ -880,6 +872,7 @@ def delete_special_charge(request, instance_id, step_id, item_id):
def final_settlement_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)
+
if not instance.can_access_step(step):
messages.error(request, 'شما به این مرحله دسترسی ندارید. ابتدا مراحل قبلی را تکمیل کنید.')
return redirect('processes:request_list')
@@ -890,6 +883,7 @@ def final_settlement_step(request, instance_id, step_id):
# 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()}
@@ -947,6 +941,13 @@ def final_settlement_step(request, instance_id, step_id):
defaults={'approved_by': request.user, 'decision': 'rejected', 'reason': reason}
)
StepRejection.objects.create(step_instance=step_instance, rejected_by=request.user, reason=reason)
+ # If current step is ahead of this step, reset it back to this step (align behavior with other steps)
+ 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('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id)
@@ -984,6 +985,7 @@ def add_final_payment(request, instance_id, step_id):
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()
@@ -1038,12 +1040,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
+
+ # 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
@@ -1065,6 +1069,16 @@ def add_final_payment(request, instance_id, step_id):
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
+
+
return JsonResponse({
'success': True,
'redirect': reverse('invoices:final_settlement_step', args=[instance.id, step_id]),
@@ -1091,14 +1105,44 @@ def delete_final_payment(request, instance_id, step_id, payment_id):
return JsonResponse({'success': False, 'message': 'شما مجوز حذف تراکنش تسویه را ندارید'}, status=403)
payment.delete()
invoice.refresh_from_db()
- # After payment change, set step back to in_progress
+
+ # 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
+
+ # Reset ALL subsequent completed steps to in_progress
+ 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':
+ # Bypass validation by using update() instead of save()
+ instance.step_instances.filter(step=subsequent_step).update(
+ status='in_progress',
+ completed_at=None
+ )
+ # Clear previous approvals if the step requires re-approval
+ 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
+
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),