fix force approve and vat show

This commit is contained in:
aminhashemi92 2025-10-07 07:45:09 +03:30
parent 65cc48769d
commit 169a9bd624
10 changed files with 133 additions and 5 deletions

View file

@ -151,6 +151,15 @@ class Quote(NameSlugModel):
remaining = Decimal('0')
return remaining
def get_vat_amount(self) -> Decimal:
"""محاسبه مبلغ مالیات به صورت جداگانه بر اساس VAT_RATE."""
base_amount = (self.total_amount or Decimal('0')) - (self.discount_amount or Decimal('0'))
try:
vat_rate = Decimal(str(getattr(settings, 'VAT_RATE', 0)))
except Exception:
vat_rate = Decimal('0')
return base_amount * vat_rate
class QuoteItem(BaseModel):
"""مدل آیتم‌های پیش‌فاکتور"""
quote = models.ForeignKey(Quote, on_delete=models.CASCADE, related_name='items', verbose_name="پیش‌فاکتور")
@ -291,6 +300,15 @@ class Invoice(NameSlugModel):
remaining = self.final_amount - paid
return remaining
def get_vat_amount(self) -> Decimal:
"""محاسبه مبلغ مالیات به صورت جداگانه بر اساس VAT_RATE."""
base_amount = (self.total_amount or Decimal('0')) - (self.discount_amount or Decimal('0'))
try:
vat_rate = Decimal(str(getattr(settings, 'VAT_RATE', 0)))
except Exception:
vat_rate = Decimal('0')
return base_amount * vat_rate
def get_status_display_with_color(self):
"""نمایش وضعیت با رنگ"""

View file

@ -153,6 +153,10 @@
<td><strong>{{ invoice.discount_amount|floatformat:0|intcomma:False }}</strong></td>
</tr>
{% endif %}
<tr class="total-section">
<td colspan="5" class="text-end"><strong>مالیات بر ارزش افزوده(تومان):</strong></td>
<td><strong>{{ invoice.get_vat_amount|floatformat:0|intcomma:False }}</strong></td>
</tr>
<tr class="total-section border-top border-2">
<td colspan="5" class="text-end"><strong>مبلغ نهایی (شامل مالیات)(تومان):</strong></td>
<td><strong>{{ invoice.final_amount|floatformat:0|intcomma:False }}</strong></td>

View file

@ -159,6 +159,10 @@
<th colspan="6" class="text-end">تخفیف</th>
<th class="text-end">{{ invoice.discount_amount|floatformat:0|intcomma:False }} تومان</th>
</tr>
<tr>
<th colspan="6" class="text-end">مالیات بر ارزش افزوده</th>
<th class="text-end">{{ invoice.get_vat_amount|floatformat:0|intcomma:False }} تومان</th>
</tr>
<tr>
<th colspan="6" class="text-end">مبلغ نهایی (با مالیات)</th>
<th class="text-end">{{ invoice.final_amount|floatformat:0|intcomma:False }} تومان</th>

View file

@ -60,7 +60,7 @@
<div class="bs-stepper-content">
<div class="row g-3">
{% if is_broker %}
{% if is_broker and invoice.get_remaining_amount != 0 %}
<div class="col-12 col-lg-5">
<div class="card border h-100">
<div class="card-header"><h5 class="mb-0">ثبت تراکنش تسویه</h5></div>
@ -193,7 +193,7 @@
</div>
</div>
</div>
{% if approver_statuses %}
{% if approver_statuses and invoice.get_remaining_amount != 0 and step_instance.status != 'completed' %}
<div class="card border mt-2">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="mb-0">وضعیت تاییدها</h6>

View file

@ -213,6 +213,7 @@
{% if quote.discount_amount > 0 %}
<p class="mb-2">تخفیف:</p>
{% endif %}
<p class="mb-2">مالیات بر ارزش افزوده:</p>
<p class="mb-0 fw-bold">مبلغ نهایی (شامل مالیات):</p>
</td>
<td class="px-4 py-5">
@ -220,6 +221,7 @@
{% if quote.discount_amount > 0 %}
<p class="fw-medium mb-2">{{ quote.discount_amount|floatformat:0|intcomma:False }} تومان</p>
{% endif %}
<p class="fw-medium mb-2">{{ quote.get_vat_amount|floatformat:0|intcomma:False }} تومان</p>
<p class="fw-bold mb-0">{{ quote.final_amount|floatformat:0|intcomma:False }} تومان</p>
</td>
</tr>

View file

@ -212,6 +212,10 @@
<td><strong>{{ quote.discount_amount|floatformat:0|intcomma:False }}</strong></td>
</tr>
{% endif %}
<tr class="total-section">
<td colspan="5" class="text-end"><strong>مالیات بر ارزش افزوده(تومان):</strong></td>
<td><strong>{{ quote.get_vat_amount|floatformat:0|intcomma:False }}</strong></td>
</tr>
<tr class="total-section border-top border-2">
<td colspan="5" class="text-end"><strong>مبلغ نهایی (با مالیات)(تومان):</strong></td>
<td><strong>{{ quote.final_amount|floatformat:0|intcomma:False }}</strong></td>

View file

@ -898,6 +898,29 @@ def add_special_charge(request, instance_id, step_id):
unit_price=amount_dec,
)
invoice.calculate_totals()
# If the next step was completed, reopen it (set to in_progress) due to invoice change
try:
step = get_object_or_404(instance.process.steps, id=step_id)
next_step = instance.process.steps.filter(order__gt=step.order).first()
if next_step:
si, _ = StepInstance.objects.get_or_create(process_instance=instance, step=next_step)
if si.status in ['completed', 'approved']:
si.status = 'in_progress'
si.completed_at = None
si.save(update_fields=['status', 'completed_at'])
# Clear prior approvals/rejections as the underlying totals changed
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
return JsonResponse({'success': True, 'redirect': reverse('invoices:final_invoice_step', args=[instance.id, step_id])})
@ -921,6 +944,29 @@ def delete_special_charge(request, instance_id, step_id, item_id):
return JsonResponse({'success': False, 'message': 'امکان حذف این مورد وجود ندارد'})
inv_item.hard_delete()
invoice.calculate_totals()
# If the next step was completed, reopen it (set to in_progress) due to invoice change
try:
step = get_object_or_404(instance.process.steps, id=step_id)
next_step = instance.process.steps.filter(order__gt=step.order).first()
if next_step:
si, _ = StepInstance.objects.get_or_create(process_instance=instance, step=next_step)
if si.status in ['completed', 'approved']:
si.status = 'in_progress'
si.completed_at = None
si.save(update_fields=['status', 'completed_at'])
# Clear prior approvals/rejections as the underlying totals changed
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
return JsonResponse({'success': True, 'redirect': reverse('invoices:final_invoice_step', args=[instance.id, step_id])})
@ -939,6 +985,23 @@ 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'})
# Auto-complete step when invoice is fully settled (no approvals needed)
try:
invoice.calculate_totals()
if invoice.get_remaining_amount() == 0:
if step_instance.status != 'completed':
step_instance.status = 'completed'
step_instance.completed_at = timezone.now()
step_instance.save()
# if next_step:
# instance.current_step = next_step
# instance.save(update_fields=['current_step'])
# return redirect('processes:step_detail', instance_id=instance.id, step_id=next_step.id)
# return redirect('processes:request_list')
except Exception:
# If totals calculation fails, continue with normal flow
pass
# Build approver statuses for template (include reason to display in UI)
reqs = list(step.approver_requirements.select_related('role').all())
@ -1048,6 +1111,14 @@ def final_settlement_step(request, instance_id, step_id):
except Exception:
messages.error(request, 'فقط مدیر مجاز به تایید اضطراری است.')
return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id)
# Allow emergency approval only when invoice has a remaining (non-zero)
try:
invoice.calculate_totals()
if invoice.get_remaining_amount() == 0:
messages.error(request, 'فاکتور تسویه شده است؛ تایید اضطراری لازم نیست.')
return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id)
except Exception:
pass
# Mark step completed regardless of remaining amount/approvals
step_instance.status = 'approved'
step_instance.save()
@ -1094,6 +1165,14 @@ def add_final_payment(request, instance_id, step_id):
except Exception:
return JsonResponse({'success': False, 'message': 'شما مجوز افزودن تراکنش تسویه را ندارید'}, status=403)
# Prevent adding payments if invoice already settled
try:
invoice.calculate_totals()
if invoice.get_remaining_amount() == 0:
return JsonResponse({'success': False, 'message': 'فاکتور تسویه شده است؛ افزودن تراکنش مجاز نیست'})
except Exception:
pass
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()