fix final invoice and certificatre
This commit is contained in:
parent
0cfa86bde3
commit
dd37ac3720
11 changed files with 120 additions and 66 deletions
|
|
@ -78,7 +78,7 @@ def certificate_step(request, instance_id, step_id):
|
||||||
if inv:
|
if inv:
|
||||||
if prev_si and not prev_si.status == 'approved':
|
if prev_si and not prev_si.status == 'approved':
|
||||||
inv.calculate_totals()
|
inv.calculate_totals()
|
||||||
if inv.remaining_amount != 0:
|
if inv.get_remaining_amount() != 0:
|
||||||
messages.error(request, 'مانده فاکتور باید صفر باشد')
|
messages.error(request, 'مانده فاکتور باید صفر باشد')
|
||||||
return redirect('processes:request_list')
|
return redirect('processes:request_list')
|
||||||
|
|
||||||
|
|
|
||||||
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
|
|
@ -44,11 +44,11 @@ class PaymentInline(admin.TabularInline):
|
||||||
|
|
||||||
@admin.register(Invoice)
|
@admin.register(Invoice)
|
||||||
class InvoiceAdmin(SimpleHistoryAdmin):
|
class InvoiceAdmin(SimpleHistoryAdmin):
|
||||||
list_display = ['name', 'process_instance', 'customer', 'status_display', 'final_amount', 'paid_amount', 'remaining_amount', 'due_date']
|
list_display = ['name', 'process_instance', 'customer', 'status_display', 'final_amount', 'paid_amount_display', 'remaining_amount_display', 'due_date']
|
||||||
list_filter = ['status', 'created', 'due_date', 'process_instance__process']
|
list_filter = ['status', 'created', 'due_date', 'process_instance__process']
|
||||||
search_fields = ['name', 'customer__username', 'customer__first_name', 'customer__last_name', 'notes']
|
search_fields = ['name', 'customer__username', 'customer__first_name', 'customer__last_name', 'notes']
|
||||||
prepopulated_fields = {'slug': ('name',)}
|
prepopulated_fields = {'slug': ('name',)}
|
||||||
readonly_fields = ['deleted_at', 'created', 'updated', 'total_amount', 'discount_amount', 'final_amount', 'paid_amount', 'remaining_amount']
|
readonly_fields = ['deleted_at', 'created', 'updated', 'total_amount', 'discount_amount', 'final_amount', 'paid_amount_display', 'remaining_amount_display']
|
||||||
inlines = [InvoiceItemInline, PaymentInline]
|
inlines = [InvoiceItemInline, PaymentInline]
|
||||||
ordering = ['-created']
|
ordering = ['-created']
|
||||||
|
|
||||||
|
|
@ -56,6 +56,16 @@ class InvoiceAdmin(SimpleHistoryAdmin):
|
||||||
return mark_safe(obj.get_status_display_with_color())
|
return mark_safe(obj.get_status_display_with_color())
|
||||||
status_display.short_description = "وضعیت"
|
status_display.short_description = "وضعیت"
|
||||||
|
|
||||||
|
def paid_amount_display(self, obj):
|
||||||
|
return f"{obj.get_paid_amount():,.0f} تومان"
|
||||||
|
paid_amount_display.short_description = "مبلغ پرداخت شده"
|
||||||
|
|
||||||
|
def remaining_amount_display(self, obj):
|
||||||
|
amount = obj.get_remaining_amount()
|
||||||
|
color = "green" if amount <= 0 else "red"
|
||||||
|
return format_html('<span style="color: {};">{:,.0f} تومان</span>', color, amount)
|
||||||
|
remaining_amount_display.short_description = "مبلغ باقیمانده"
|
||||||
|
|
||||||
@admin.register(Payment)
|
@admin.register(Payment)
|
||||||
class PaymentAdmin(SimpleHistoryAdmin):
|
class PaymentAdmin(SimpleHistoryAdmin):
|
||||||
list_display = ['invoice', 'amount', 'payment_method', 'payment_date', 'created_by']
|
list_display = ['invoice', 'amount', 'payment_method', 'payment_date', 'created_by']
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Generated by Django 5.2.4 on 2025-10-04 08:16
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('invoices', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='historicalinvoice',
|
||||||
|
name='paid_amount',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='historicalinvoice',
|
||||||
|
name='remaining_amount',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='invoice',
|
||||||
|
name='paid_amount',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='invoice',
|
||||||
|
name='remaining_amount',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -228,18 +228,6 @@ class Invoice(NameSlugModel):
|
||||||
default=0,
|
default=0,
|
||||||
verbose_name="مبلغ نهایی"
|
verbose_name="مبلغ نهایی"
|
||||||
)
|
)
|
||||||
paid_amount = models.DecimalField(
|
|
||||||
max_digits=15,
|
|
||||||
decimal_places=2,
|
|
||||||
default=0,
|
|
||||||
verbose_name="مبلغ پرداخت شده"
|
|
||||||
)
|
|
||||||
remaining_amount = models.DecimalField(
|
|
||||||
max_digits=15,
|
|
||||||
decimal_places=2,
|
|
||||||
default=0,
|
|
||||||
verbose_name="مبلغ باقیمانده"
|
|
||||||
)
|
|
||||||
due_date = models.DateField(verbose_name="تاریخ سررسید")
|
due_date = models.DateField(verbose_name="تاریخ سررسید")
|
||||||
notes = models.TextField(verbose_name="یادداشتها", blank=True)
|
notes = models.TextField(verbose_name="یادداشتها", blank=True)
|
||||||
created_by = models.ForeignKey(
|
created_by = models.ForeignKey(
|
||||||
|
|
@ -278,22 +266,31 @@ class Invoice(NameSlugModel):
|
||||||
vat_amount = base_amount * vat_rate
|
vat_amount = base_amount * vat_rate
|
||||||
self.final_amount = base_amount + vat_amount
|
self.final_amount = base_amount + vat_amount
|
||||||
|
|
||||||
# خالص مانده به نفع شرکت (مثبت) یا به نفع مشتری (منفی)
|
# وضعیت بر اساس مانده خالص (استفاده از تابعها)
|
||||||
net_due = self.final_amount - self.paid_amount
|
paid = self.get_paid_amount()
|
||||||
self.remaining_amount = net_due
|
net_due = self.final_amount - paid
|
||||||
|
|
||||||
# وضعیت بر اساس مانده خالص
|
|
||||||
if net_due == 0:
|
if net_due == 0:
|
||||||
self.status = 'paid'
|
self.status = 'paid'
|
||||||
elif net_due > 0:
|
elif net_due > 0:
|
||||||
# مشتری هنوز باید پرداخت کند
|
# مشتری هنوز باید پرداخت کند
|
||||||
self.status = 'partially_paid' if self.paid_amount > 0 else 'sent'
|
self.status = 'partially_paid' if paid > 0 else 'sent'
|
||||||
else:
|
else:
|
||||||
# شرکت باید به مشتری پرداخت کند
|
# شرکت باید به مشتری پرداخت کند
|
||||||
self.status = 'partially_paid'
|
self.status = 'partially_paid'
|
||||||
|
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
def get_paid_amount(self):
|
||||||
|
"""مبلغ پرداخت شده بر اساس پرداختها (مثل Quote)"""
|
||||||
|
return sum((p.amount if p.direction == 'in' else -p.amount) for p in self.payments.filter(is_deleted=False).all())
|
||||||
|
|
||||||
|
def get_remaining_amount(self):
|
||||||
|
"""مبلغ باقیمانده بر اساس پرداختها (مثل Quote)"""
|
||||||
|
paid = self.get_paid_amount()
|
||||||
|
remaining = self.final_amount - paid
|
||||||
|
return remaining
|
||||||
|
|
||||||
|
|
||||||
def get_status_display_with_color(self):
|
def get_status_display_with_color(self):
|
||||||
"""نمایش وضعیت با رنگ"""
|
"""نمایش وضعیت با رنگ"""
|
||||||
|
|
@ -373,17 +370,13 @@ class Payment(BaseModel):
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""بروزرسانی مبالغ فاکتور"""
|
"""بروزرسانی مبالغ فاکتور"""
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
# بروزرسانی مبلغ پرداخت شده فاکتور
|
# فقط مجدداً calculate_totals را صدا کن (مثل Quote)
|
||||||
total_paid = sum((p.amount if p.direction == 'in' else -p.amount) for p in self.invoice.payments.filter(is_deleted=False).all())
|
|
||||||
self.invoice.paid_amount = total_paid
|
|
||||||
self.invoice.calculate_totals()
|
self.invoice.calculate_totals()
|
||||||
|
|
||||||
def delete(self, using=None, keep_parents=False):
|
def delete(self, using=None, keep_parents=False):
|
||||||
"""حذف نرم و بروزرسانی مبالغ فاکتور پس از حذف"""
|
"""حذف نرم و بروزرسانی مبالغ فاکتور پس از حذف"""
|
||||||
result = super().delete(using=using, keep_parents=keep_parents)
|
result = super().delete(using=using, keep_parents=keep_parents)
|
||||||
try:
|
try:
|
||||||
total_paid = sum((p.amount if p.direction == 'in' else -p.amount) for p in self.invoice.payments.filter(is_deleted=False).all())
|
|
||||||
self.invoice.paid_amount = total_paid
|
|
||||||
self.invoice.calculate_totals()
|
self.invoice.calculate_totals()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -159,11 +159,11 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="total-section">
|
<tr class="total-section">
|
||||||
<td colspan="5" class="text-end"><strong>پرداختیها(تومان):</strong></td>
|
<td colspan="5" class="text-end"><strong>پرداختیها(تومان):</strong></td>
|
||||||
<td><strong">{{ invoice.paid_amount|floatformat:0|intcomma:False }}</strong></td>
|
<td><strong">{{ invoice.get_paid_amount|floatformat:0|intcomma:False }}</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="total-section">
|
<tr class="total-section">
|
||||||
<td colspan="5" class="text-end"><strong>مانده(تومان):</strong></td>
|
<td colspan="5" class="text-end"><strong>مانده(تومان):</strong></td>
|
||||||
<td><strong>{{ invoice.remaining_amount|floatformat:0|intcomma:False }}</strong></td>
|
<td><strong>{{ invoice.get_remaining_amount|floatformat:0|intcomma:False }}</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
||||||
|
|
@ -74,17 +74,17 @@
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
<div class="border rounded p-3 h-100">
|
<div class="border rounded p-3 h-100">
|
||||||
<div class="small text-muted">پرداختیها</div>
|
<div class="small text-muted">پرداختیها</div>
|
||||||
<div class="h5 mt-1 text-success">{{ invoice.paid_amount|floatformat:0|intcomma:False }} تومان</div>
|
<div class="h5 mt-1 text-success">{{ invoice.get_paid_amount|floatformat:0|intcomma:False }} تومان</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
<div class="border rounded p-3 h-100">
|
<div class="border rounded p-3 h-100">
|
||||||
<div class="small text-muted">مانده</div>
|
<div class="small text-muted">مانده</div>
|
||||||
<div class="h5 mt-1 {% if invoice.remaining_amount <= 0 %}text-success{% else %}text-danger{% endif %}">{{ invoice.remaining_amount|floatformat:0|intcomma:False }} تومان</div>
|
<div class="h5 mt-1 {% if invoice.get_remaining_amount <= 0 %}text-success{% else %}text-danger{% endif %}">{{ invoice.get_remaining_amount|floatformat:0|intcomma:False }} تومان</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 col-md-3 d-flex align-items-center">
|
<div class="col-6 col-md-3 d-flex align-items-center">
|
||||||
{% if invoice.remaining_amount <= 0 %}
|
{% if invoice.get_remaining_amount <= 0 %}
|
||||||
<span class="badge bg-success">تسویه کامل</span>
|
<span class="badge bg-success">تسویه کامل</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-warning text-dark">باقیمانده دارد</span>
|
<span class="badge bg-warning text-dark">باقیمانده دارد</span>
|
||||||
|
|
@ -165,11 +165,11 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="6" class="text-end">پرداختیها</th>
|
<th colspan="6" class="text-end">پرداختیها</th>
|
||||||
<th class="text-end">{{ invoice.paid_amount|floatformat:0|intcomma:False }} تومان</th>
|
<th class="text-end">{{ invoice.get_paid_amount|floatformat:0|intcomma:False }} تومان</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="6" class="text-end">مانده</th>
|
<th colspan="6" class="text-end">مانده</th>
|
||||||
<th class="text-end {% if invoice.remaining_amount <= 0 %}text-success{% else %}text-danger{% endif %}">{{ invoice.remaining_amount|floatformat:0|intcomma:False }} تومان</th>
|
<th class="text-end {% if invoice.get_remaining_amount <= 0 %}text-success{% else %}text-danger{% endif %}">{{ invoice.get_remaining_amount|floatformat:0|intcomma:False }} تومان</th>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
<a href="{% url 'invoices:final_invoice_print' instance.id %}" target="_blank" class="btn btn-outline-secondary">
|
<a href="{% url 'invoices:final_invoice_print' instance.id %}" target="_blank" class="btn btn-outline-secondary">
|
||||||
<i class="bx bx-printer me-2"></i> پرینت
|
<i class="bx bx-printer me-2"></i> پرینت
|
||||||
</a>
|
</a>
|
||||||
{% if request.user|is_manager and step_instance.status != 'approved' and step_instance.status != 'completed' and invoice.remaining_amount != 0 %}
|
{% if request.user|is_manager and step_instance.status != 'approved' and step_instance.status != 'completed' and invoice.get_remaining_amount != 0 %}
|
||||||
<button type="button" class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#forceApproveModal">
|
<button type="button" class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#forceApproveModal">
|
||||||
<i class="bx bx-bolt-circle me-1"></i> تایید اضطراری
|
<i class="bx bx-bolt-circle me-1"></i> تایید اضطراری
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -128,17 +128,17 @@
|
||||||
<div class="col-6 col-md-4">
|
<div class="col-6 col-md-4">
|
||||||
<div class="border rounded p-3 h-100">
|
<div class="border rounded p-3 h-100">
|
||||||
<div class="small text-muted">پرداختیها</div>
|
<div class="small text-muted">پرداختیها</div>
|
||||||
<div class="h5 mt-1 text-success">{{ invoice.paid_amount|floatformat:0|intcomma:False }} تومان</div>
|
<div class="h5 mt-1 text-success">{{ invoice.get_paid_amount|floatformat:0|intcomma:False }} تومان</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 col-md-4">
|
<div class="col-6 col-md-4">
|
||||||
<div class="border rounded p-3 h-100">
|
<div class="border rounded p-3 h-100">
|
||||||
<div class="small text-muted">مانده</div>
|
<div class="small text-muted">مانده</div>
|
||||||
<div class="h5 mt-1 {% if invoice.remaining_amount <= 0 %}text-success{% else %}text-danger{% endif %}">{{ invoice.remaining_amount|floatformat:0|intcomma:False }} تومان</div>
|
<div class="h5 mt-1 {% if invoice.get_remaining_amount <= 0 %}text-success{% else %}text-danger{% endif %}">{{ invoice.get_remaining_amount|floatformat:0|intcomma:False }} تومان</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 d-flex align-items-center">
|
<div class="col-6 d-flex align-items-center">
|
||||||
{% if invoice.remaining_amount <= 0 %}
|
{% if invoice.get_remaining_amount <= 0 %}
|
||||||
<span class="badge bg-success">تسویه کامل</span>
|
<span class="badge bg-success">تسویه کامل</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-warning text-dark">باقیمانده دارد</span>
|
<span class="badge bg-warning text-dark">باقیمانده دارد</span>
|
||||||
|
|
@ -318,9 +318,9 @@
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
{% if invoice.remaining_amount != 0 %}
|
{% if invoice.get_remaining_amount != 0 %}
|
||||||
<div class="alert alert-warning" role="alert">
|
<div class="alert alert-warning" role="alert">
|
||||||
مانده فاکتور: <strong>{{ invoice.remaining_amount|floatformat:0|intcomma:False }} تومان</strong><br>
|
مانده فاکتور: <strong>{{ invoice.get_remaining_amount|floatformat:0|intcomma:False }} تومان</strong><br>
|
||||||
امکان تایید تا تسویه کامل فاکتور وجود ندارد.
|
امکان تایید تا تسویه کامل فاکتور وجود ندارد.
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
@ -329,7 +329,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-label-secondary" data-bs-dismiss="modal">انصراف</button>
|
<button type="button" class="btn btn-label-secondary" data-bs-dismiss="modal">انصراف</button>
|
||||||
<button type="submit" class="btn btn-success" {% if invoice.remaining_amount != 0 %}disabled{% endif %}>تایید</button>
|
<button type="submit" class="btn btn-success" {% if invoice.get_remaining_amount != 0 %}disabled{% endif %}>تایید</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -942,8 +942,8 @@ def final_settlement_step(request, instance_id, step_id):
|
||||||
|
|
||||||
# Build approver statuses for template (include reason to display in UI)
|
# Build approver statuses for template (include reason to display in UI)
|
||||||
reqs = list(step.approver_requirements.select_related('role').all())
|
reqs = list(step.approver_requirements.select_related('role').all())
|
||||||
approvals = list(step_instance.approvals.select_related('role').all())
|
approvals = list(step_instance.approvals.select_related('role', 'approved_by').filter(is_deleted=False))
|
||||||
rejections = list(step_instance.rejections.select_related('role').all())
|
rejections = list(step_instance.rejections.select_related('role', 'rejected_by').filter(is_deleted=False))
|
||||||
approvals_by_role = {a.role_id: a for a in approvals}
|
approvals_by_role = {a.role_id: a for a in approvals}
|
||||||
rejections_by_role = {r.role_id: r for r in rejections}
|
rejections_by_role = {r.role_id: r for r in rejections}
|
||||||
approver_statuses = []
|
approver_statuses = []
|
||||||
|
|
@ -978,8 +978,8 @@ def final_settlement_step(request, instance_id, step_id):
|
||||||
# Compute whether current user has already decided (approved/rejected)
|
# Compute whether current user has already decided (approved/rejected)
|
||||||
current_user_has_decided = False
|
current_user_has_decided = False
|
||||||
try:
|
try:
|
||||||
user_has_approval = step_instance.approvals.filter(approved_by=request.user).exists()
|
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).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)
|
current_user_has_decided = bool(user_has_approval or user_has_rejection)
|
||||||
except Exception:
|
except Exception:
|
||||||
current_user_has_decided = False
|
current_user_has_decided = False
|
||||||
|
|
@ -997,8 +997,8 @@ def final_settlement_step(request, instance_id, step_id):
|
||||||
if action == 'approve':
|
if action == 'approve':
|
||||||
# enforce zero remaining
|
# enforce zero remaining
|
||||||
invoice.calculate_totals()
|
invoice.calculate_totals()
|
||||||
if invoice.remaining_amount != 0:
|
if invoice.get_remaining_amount() != 0:
|
||||||
messages.error(request, f"تا زمانی که مانده فاکتور صفر نشده امکان تایید نیست (مانده فعلی: {invoice.remaining_amount})")
|
messages.error(request, f"تا زمانی که مانده فاکتور صفر نشده امکان تایید نیست (مانده فعلی: {invoice.get_remaining_amount()})")
|
||||||
return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id)
|
return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id)
|
||||||
StepApproval.objects.create(
|
StepApproval.objects.create(
|
||||||
step_instance=step_instance,
|
step_instance=step_instance,
|
||||||
|
|
@ -1203,8 +1203,8 @@ def add_final_payment(request, instance_id, step_id):
|
||||||
'redirect': reverse('invoices:final_settlement_step', args=[instance.id, step_id]),
|
'redirect': reverse('invoices:final_settlement_step', args=[instance.id, step_id]),
|
||||||
'totals': {
|
'totals': {
|
||||||
'final_amount': str(invoice.final_amount),
|
'final_amount': str(invoice.final_amount),
|
||||||
'paid_amount': str(invoice.paid_amount),
|
'paid_amount': str(invoice.get_paid_amount()),
|
||||||
'remaining_amount': str(invoice.remaining_amount),
|
'remaining_amount': str(invoice.get_remaining_amount()),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1216,14 +1216,17 @@ def delete_final_payment(request, instance_id, step_id, payment_id):
|
||||||
step = get_object_or_404(instance.process.steps, id=step_id)
|
step = get_object_or_404(instance.process.steps, id=step_id)
|
||||||
invoice = get_object_or_404(Invoice, process_instance=instance)
|
invoice = get_object_or_404(Invoice, process_instance=instance)
|
||||||
payment = get_object_or_404(Payment, id=payment_id, invoice=invoice)
|
payment = get_object_or_404(Payment, id=payment_id, invoice=invoice)
|
||||||
|
|
||||||
# Only BROKER can delete final settlement payments
|
# Only BROKER can delete final settlement payments
|
||||||
try:
|
try:
|
||||||
if not (hasattr(request.user, 'profile') and request.user.profile.has_role(UserRoles.BROKER)):
|
if not (hasattr(request.user, 'profile') and request.user.profile.has_role(UserRoles.BROKER)):
|
||||||
return JsonResponse({'success': False, 'message': 'شما مجوز حذف تراکنش تسویه را ندارید'}, status=403)
|
return JsonResponse({'success': False, 'message': 'شما مجوز حذف تراکنش تسویه را ندارید'}, status=403)
|
||||||
except Exception:
|
except Exception:
|
||||||
return JsonResponse({'success': False, 'message': 'شما مجوز حذف تراکنش تسویه را ندارید'}, status=403)
|
return JsonResponse({'success': False, 'message': 'شما مجوز حذف تراکنش تسویه را ندارید'}, status=403)
|
||||||
|
|
||||||
|
# Delete payment and recalculate invoice totals
|
||||||
payment.hard_delete()
|
payment.hard_delete()
|
||||||
invoice.refresh_from_db()
|
invoice.calculate_totals() # This is what was missing!
|
||||||
|
|
||||||
# On delete, return to awaiting approval
|
# On delete, return to awaiting approval
|
||||||
try:
|
try:
|
||||||
|
|
@ -1231,16 +1234,11 @@ def delete_final_payment(request, instance_id, step_id, payment_id):
|
||||||
si.status = 'in_progress'
|
si.status = 'in_progress'
|
||||||
si.completed_at = None
|
si.completed_at = None
|
||||||
si.save()
|
si.save()
|
||||||
try:
|
# Clear approvals and rejections (like in quote_payment)
|
||||||
for appr in list(si.approvals.all()):
|
for appr in list(si.approvals.all()):
|
||||||
appr.delete()
|
appr.delete()
|
||||||
except Exception:
|
for rej in list(si.rejections.all()):
|
||||||
pass
|
rej.delete()
|
||||||
try:
|
|
||||||
for rej in list(si.rejections.all()):
|
|
||||||
rej.delete()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -1274,7 +1272,7 @@ def delete_final_payment(request, instance_id, step_id, payment_id):
|
||||||
|
|
||||||
return JsonResponse({'success': True, 'redirect': reverse('invoices:final_settlement_step', args=[instance.id, step_id]), 'totals': {
|
return JsonResponse({'success': True, 'redirect': reverse('invoices:final_settlement_step', args=[instance.id, step_id]), 'totals': {
|
||||||
'final_amount': str(invoice.final_amount),
|
'final_amount': str(invoice.final_amount),
|
||||||
'paid_amount': str(invoice.paid_amount),
|
'paid_amount': str(invoice.get_paid_amount()),
|
||||||
'remaining_amount': str(invoice.remaining_amount),
|
'remaining_amount': str(invoice.get_remaining_amount()),
|
||||||
}})
|
}})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,9 @@
|
||||||
<i class="bx bx-printer me-2"></i> پرینت فاکتور
|
<i class="bx bx-printer me-2"></i> پرینت فاکتور
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'certificates:certificate_print' instance.id %}" target="_blank" class="btn btn-outline-secondary">
|
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#printHologramModal">
|
||||||
<i class="bx bx-printer me-2"></i> پرینت گواهی
|
<i class="bx bx-printer me-2"></i> پرینت گواهی
|
||||||
</a>
|
</button>
|
||||||
<a href="{% url 'processes:request_list' %}" class="btn btn-outline-secondary">
|
<a href="{% url 'processes:request_list' %}" class="btn btn-outline-secondary">
|
||||||
<i class="bx bx-chevron-right bx-sm ms-sm-n2"></i>
|
<i class="bx bx-chevron-right bx-sm ms-sm-n2"></i>
|
||||||
بازگشت
|
بازگشت
|
||||||
|
|
@ -57,8 +57,8 @@
|
||||||
{% if invoice %}
|
{% if invoice %}
|
||||||
<div class="row g-3 mb-3">
|
<div class="row g-3 mb-3">
|
||||||
<div class="col-6 col-md-3"><div class="border rounded p-3 h-100"><div class="small text-muted">مبلغ نهایی</div><div class="h5 mt-1">{{ invoice.final_amount|floatformat:0|intcomma:False }} تومان</div></div></div>
|
<div class="col-6 col-md-3"><div class="border rounded p-3 h-100"><div class="small text-muted">مبلغ نهایی</div><div class="h5 mt-1">{{ invoice.final_amount|floatformat:0|intcomma:False }} تومان</div></div></div>
|
||||||
<div class="col-6 col-md-3"><div class="border rounded p-3 h-100"><div class="small text-muted">پرداختیها</div><div class="h5 mt-1 text-success">{{ invoice.paid_amount|floatformat:0|intcomma:False }} تومان</div></div></div>
|
<div class="col-6 col-md-3"><div class="border rounded p-3 h-100"><div class="small text-muted">پرداختیها</div><div class="h5 mt-1 text-success">{{ invoice.get_paid_amount|floatformat:0|intcomma:False }} تومان</div></div></div>
|
||||||
<div class="col-6 col-md-3"><div class="border rounded p-3 h-100"><div class="small text-muted">مانده</div><div class="h5 mt-1 {% if invoice.remaining_amount <= 0 %}text-success{% else %}text-danger{% endif %}">{{ invoice.remaining_amount|floatformat:0|intcomma:False }} تومان</div></div></div>
|
<div class="col-6 col-md-3"><div class="border rounded p-3 h-100"><div class="small text-muted">مانده</div><div class="h5 mt-1 {% if invoice.get_remaining_amount <= 0 %}text-success{% else %}text-danger{% endif %}">{{ invoice.get_remaining_amount|floatformat:0|intcomma:False }} تومان</div></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped mb-0">
|
<table class="table table-striped mb-0">
|
||||||
|
|
@ -256,6 +256,30 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Print Hologram Modal -->
|
||||||
|
<div class="modal fade" id="printHologramModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form method="post" action="{% url 'certificates:certificate_print' instance.id %}" target="_blank">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">کد یکتا هولوگرام</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<label class="form-label">کد هولوگرام</label>
|
||||||
|
<input type="text" class="form-control" name="hologram_code" value="{{ certificate.hologram_code|default:'' }}" placeholder="مثال: 123456" required>
|
||||||
|
<div class="form-text">این کد باید با کد هولوگرام روی گواهی یکسان باشد.</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-label-secondary" data-bs-dismiss="modal">انصراف</button>
|
||||||
|
<button type="submit" class="btn btn-primary">ثبت و پرینت</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -643,7 +643,7 @@ def export_requests_excel(request):
|
||||||
).select_related('process_instance')
|
).select_related('process_instance')
|
||||||
|
|
||||||
for invoice in invoices:
|
for invoice in invoices:
|
||||||
if invoice.remaining_amount == 0: # Fully settled
|
if invoice.get_remaining_amount() == 0: # Fully settled
|
||||||
# Find the last payment date for this invoice
|
# Find the last payment date for this invoice
|
||||||
last_payment = Payment.objects.filter(
|
last_payment = Payment.objects.filter(
|
||||||
invoice__process_instance=invoice.process_instance,
|
invoice__process_instance=invoice.process_instance,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue