fix date in payment and imporove contract page.

This commit is contained in:
aminhashemi92 2025-09-08 16:55:43 +03:30
parent af40e169ae
commit 204b0aa48e
14 changed files with 295 additions and 74 deletions

View file

@ -33,7 +33,7 @@ class ProfileAdmin(admin.ModelAdmin):
@admin.register(Company) @admin.register(Company)
class CompanyAdmin(admin.ModelAdmin): class CompanyAdmin(admin.ModelAdmin):
list_display = ['name', 'logo', 'signature', 'address', 'phone', 'broker'] list_display = ['name', 'logo', 'signature', 'address', 'phone', 'broker', 'registration_number']
prepopulated_fields = {'slug': ('name',)} prepopulated_fields = {'slug': ('name',)}
search_fields = ['name', 'address', 'phone'] search_fields = ['name', 'address', 'phone']
list_filter = ['is_active', 'broker'] list_filter = ['is_active', 'broker']

View file

@ -0,0 +1,18 @@
# Generated by Django 5.2.4 on 2025-09-08 10:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0004_company_branch_name'),
]
operations = [
migrations.AddField(
model_name='company',
name='registration_number',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='شماره ثبت شرکت'),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 5.2.4 on 2025-09-08 10:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0005_company_registration_number'),
]
operations = [
migrations.AddField(
model_name='company',
name='card_holder_name',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='نام دارنده کارت'),
),
]

View file

@ -204,6 +204,12 @@ class Company(NameSlugModel):
blank=True, blank=True,
verbose_name='شماره تماس' verbose_name='شماره تماس'
) )
registration_number = models.CharField(
max_length=255,
null=True,
blank=True,
verbose_name='شماره ثبت شرکت'
)
broker = models.OneToOneField( broker = models.OneToOneField(
Broker, Broker,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
@ -238,6 +244,12 @@ class Company(NameSlugModel):
) )
] ]
) )
card_holder_name = models.CharField(
max_length=255,
null=True,
verbose_name="نام دارنده کارت",
blank=True,
)
sheba_number = models.CharField( sheba_number = models.CharField(
max_length=30, max_length=30,
null=True, null=True,

View file

@ -2,45 +2,71 @@
<html lang="fa" dir="rtl"> <html lang="fa" dir="rtl">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>چاپ قرارداد {{ instance.code }}</title> <title>چاپ قرارداد {{ instance.code }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> {% load static %}
<!-- Match app fonts and theme -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Public+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500,1,600,1,700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{% static 'assets/vendor/fonts/boxicons.css' %}">
<link rel="stylesheet" href="{% static 'assets/vendor/fonts/fontawesome.css' %}">
<link rel="stylesheet" href="{% static 'assets/vendor/css/rtl/core.css' %}">
<link rel="stylesheet" href="{% static 'assets/vendor/css/rtl/theme-default.css' %}">
<link rel="stylesheet" href="{% static 'assets/css/demo.css' %}">
<link rel="stylesheet" href="{% static 'assets/css/persian-fonts.css' %}">
<style> <style>
@page { size: A4; margin: 1.2cm; } @page { size: A4; margin: 1.2cm; }
body { font-family: 'Vazirmatn', sans-serif; } .invoice-header { border-bottom: 1px solid #dee2e6; padding-bottom: 16px; margin-bottom: 24px; }
.logo { max-height: 80px; } .brand-box { width:64px; height:64px; display:flex; align-items:center; justify-content:center; background:#eef2ff; border-radius:8px; }
.signature { height: 90px; border: 1px dashed #ccc; } .logo { max-height: 58px; max-width: 120px; }
.contract-title { font-size: 20px; font-weight: 600; }
.small-muted { font-size: 12px; color: #6c757d; }
.signature-box { border: 1px dashed #ccc; height: 210px; display:flex; align-items:center; justify-content:center; }
</style> </style>
<script> <script>
window.addEventListener('load', function(){ setTimeout(function(){ window.print(); }, 300); }); window.onload = function(){
window.print();
setTimeout(function(){ window.close(); }, 200);
};
</script> </script>
</head> </head>
<body> <body>
<div class="container-fluid"> <div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-3"> <!-- Header: Company and contract info -->
<div> <div class="invoice-header">
<h5>{{ contract.template.company.name }}</h5> <div class="small text-end text-muted mb-2">تاریخ: {{ contract.jcreated_date }} | کد درخواست: {{ instance.code }}</div>
<h5 class="mb-1">{{ contract.template.name }}</h5> <h5 class="text-center mb-3">
<div class="text-muted small">کد درخواست: {{ instance.code }} | تاریخ: {{ contract.jcreated }}</div> {% if instance.broker and instance.broker.company %}
</div> {{ instance.broker.company.name }}
{% if contract.template.company.logo %} {% elif template.company %}
<img class="logo" src="{{ contract.template.company.logo.url }}" alt="لوگو" /> {{ template.company.name }}
{% else %}
شرکت آب منطقه‌ای
{% endif %} {% endif %}
</h5>
<h4 class="text-center mb-3">{{ contract.template.name }}</h4>
</div> </div>
<hr>
<!-- Contract body -->
<div style="white-space: pre-line; line-height: 1.9;">{{ contract.rendered_body|safe }}</div> <div style="white-space: pre-line; line-height: 1.9;">{{ contract.rendered_body|safe }}</div>
<hr> <hr>
<!-- Signatures -->
<div class="row mt-4"> <div class="row mt-4">
<div class="col-6 text-center"> <div class="col-6 text-center">
<div>امضای مشترک</div> <div>امضای مشترک</div>
<div class="signature mt-2"></div> <div class="signature-box mt-2"></div>
</div> </div>
<div class="col-6 text-center"> <div class="col-6 text-center">
<div>امضای شرکت</div> <div>امضای شرکت</div>
<div class="signature mt-2"> <div class="signature-box mt-2">
{% if contract.template.company.signature %} {% if instance.broker and instance.broker.company and instance.broker.company.signature %}
<img src="{{ contract.template.company.signature.url }}" alt="امضای شرکت" style="max-height: 80px;" /> <img src="{{ instance.broker.company.signature.url }}" alt="امضای شرکت" style="max-height: 200px;" />
{% elif contract.template.company and contract.template.company.signature %}
<img src="{{ contract.template.company.signature.url }}" alt="امضای شرکت" style="max-height: 200px;" />
{% endif %} {% endif %}
</div> </div>
</div> </div>

View file

@ -19,6 +19,10 @@
{% block content %} {% block content %}
{% include '_toasts.html' %} {% include '_toasts.html' %}
<!-- Instance Info Modal -->
{% instance_info_modal instance %}
<div class="container-xxl flex-grow-1 container-p-y"> <div class="container-xxl flex-grow-1 container-p-y">
<div class="row"> <div class="row">
<div class="col-12 mb-4"> <div class="col-12 mb-4">
@ -26,13 +30,18 @@
<div> <div>
<h4 class="mb-1">{{ step.name }}: {{ instance.process.name }}</h4> <h4 class="mb-1">{{ step.name }}: {{ instance.process.name }}</h4>
<small class="text-muted d-block"> <small class="text-muted d-block">
اشتراک آب: {{ instance.well.water_subscription_number|default:"-" }} {% instance_info instance %}
| نماینده: {{ instance.representative.profile.national_code|default:"-" }}
</small> </small>
</div> </div>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<a href="{% url 'processes:request_list' %}" class="btn btn-outline-secondary">بازگشت</a> <a href="{% url 'contracts:contract_print' instance.id %}" target="_blank" class="btn btn-outline-secondary">
<a href="{% url 'contracts:contract_print' instance.id %}" target="_blank" class="btn btn-outline-secondary">پرینت</a> <i class="bx bx-printer me-2"></i> پرینت
</a>
<a href="{% url 'processes:request_list' %}" class="btn btn-outline-secondary">
<i class="bx bx-chevron-right bx-sm ms-sm-n2"></i>
بازگشت
</a>
</div> </div>
</div> </div>
@ -41,29 +50,32 @@
<div class="bs-stepper-content"> <div class="bs-stepper-content">
<div class="card border"> <div class="card border">
<div class="card-body"> <div class="card-body">
<div class="small text-end text-muted mb-2">تاریخ: {{ contract.jcreated_date }} | کد درخواست: {{ instance.code }}</div>
<h5 class="text-center mb-3">
{% if instance.broker and instance.broker.company %}
{{ instance.broker.company.name }}
{% elif template.company %}
{{ template.company.name }}
{% else %}
شرکت آب منطقه‌ای
{% endif %}</h5>
<h4 class="text-center mb-3">{{ contract.template.name }}</h4>
{% if can_view_contract_body %} {% if can_view_contract_body %}
{% if template.company.logo %}
<div class="text-center mb-3">
<img src="{{ template.company.logo.url }}" alt="لوگوی شرکت" style="max-height:80px;">
<h4 class="text-muted">{{ contract.template.company.name }}</h4>
<h5 class="text-muted">{{ contract.template.name }}</h5>
</div>
{% endif %}
<div class="small text-muted mb-2">تاریخ: {{ contract.jcreated }}</div>
<hr> <hr>
<div class="contract-body" style="white-space: pre-line; line-height:1.9;">{{ contract.rendered_body|safe }}</div> <div class="contract-body" style="white-space: pre-line; line-height:1.9;">{{ contract.rendered_body|safe }}</div>
<hr> <hr>
<div class="row mt-4"> <div class="row mt-4">
<div class="col-6 text-center"> <div class="col-6 text-center">
<div>امضای مشترک</div> <div>امضای مشترک</div>
<div style="height:90px;border:1px dashed #ccc; margin-top:10px;"></div> <div style="height:210px;border:1px dashed #ccc; margin-top:10px;"></div>
</div> </div>
<div class="col-6 text-center"> <div class="col-6 text-center">
<div>امضای شرکت</div> <div>امضای شرکت</div>
<div style="height:90px;border:1px dashed #ccc; margin-top:10px;"> <div style="height:210px;border:1px dashed #ccc; margin-top:10px;">
{% if template.company.signature %} {% if instance.broker and instance.broker.company and instance.broker.company.signature %}
<img src="{{ template.company.signature.url }}" alt="امضای شرکت" style="max-height:80px;"> <img src="{{ instance.broker.company.signature.url }}" alt="امضای شرکت" style="max-height:200px;">
{% elif template.company and template.company.signature %}
<img src="{{ template.company.signature.url }}" alt="امضای شرکت" style="max-height:200px;">
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -76,15 +88,23 @@
<form method="post" class="d-flex justify-content-between mt-3"> <form method="post" class="d-flex justify-content-between mt-3">
{% csrf_token %} {% csrf_token %}
{% if previous_step %} {% if previous_step %}
<a href="{% url 'processes:step_detail' instance.id previous_step.id %}" class="btn btn-label-secondary">قبلی</a> <a href="{% url 'processes:step_detail' instance.id previous_step.id %}" class="btn btn-label-secondary">
<i class="bx bx-chevron-right bx-sm me-sm-2"></i>
قبلی
</a>
{% else %} {% else %}
<span></span> <span></span>
{% endif %} {% endif %}
{% if next_step %} {% if next_step %}
{% if is_broker %} {% if is_broker %}
<button type="submit" class="btn btn-primary">تایید و بعدی</button> <button type="submit" class="btn btn-primary">تایید و بعدی
<i class="bx bx-chevron-left bx-sm me-sm-n2"></i>
</button>
{% else %} {% else %}
<a href="{% url 'processes:step_detail' instance.id next_step.id %}" class="btn btn-primary">بعدی</a> <a href="{% url 'processes:step_detail' instance.id next_step.id %}" class="btn btn-primary">
بعدی
<i class="bx bx-chevron-left bx-sm me-sm-n2"></i>
</a>
{% endif %} {% endif %}
{% else %} {% else %}
{% if is_broker %} {% if is_broker %}

View file

@ -2,29 +2,51 @@ from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from decimal import Decimal
from django.template import Template, Context from django.template import Template, Context
from django.utils.safestring import mark_safe
from processes.models import ProcessInstance, StepInstance from processes.models import ProcessInstance, StepInstance
from common.consts import UserRoles from common.consts import UserRoles
from .models import ContractTemplate, ContractInstance from .models import ContractTemplate, ContractInstance
from invoices.models import Invoice, Quote
from _helpers.utils import jalali_converter2 from _helpers.utils import jalali_converter2
from django.http import JsonResponse
def build_contract_context(instance: ProcessInstance) -> dict: def build_contract_context(instance: ProcessInstance) -> dict:
representative = instance.representative representative = instance.representative
profile = getattr(representative, 'profile', None) profile = getattr(representative, 'profile', None)
well = instance.well well = instance.well
# Compute prepayment from Quote-linked invoice payments
quote = Quote.objects.filter(process_instance=instance).first()
invoice = Invoice.objects.filter(quote=quote).first() if quote else None
payments_qs = invoice.payments.filter(is_deleted=False, direction='in').all() if invoice else []
total_paid = sum((p.amount for p in payments_qs), Decimal('0'))
try:
latest_payment_date = max((p.payment_date for p in payments_qs)) if payments_qs else None
except Exception:
latest_payment_date = None
return { return {
'customer_full_name': representative.get_full_name() if representative else '', 'customer_full_name': mark_safe(f"<span class=\"fw-bold\">{representative.get_full_name() if representative else ''}</span>"),
'national_code': profile.national_code if profile else '', 'registration_number': mark_safe(f"<span class=\"fw-bold\">{instance.broker.company.registration_number if instance.broker and instance.broker.company else ''}</span>"),
'address': profile.address if profile else '', 'national_code': mark_safe(f"<span class=\"fw-bold\">{profile.national_code if profile else ''}</span>"),
'phone': profile.phone_number_1 if profile else '', 'address': mark_safe(f"<span class=\"fw-bold\">{profile.address if profile else ''}</span>"),
'phone2': profile.phone_number_2 if profile else '', 'phone': mark_safe(f"<span class=\"fw-bold\">{profile.phone_number_1 if profile else ''}</span>"),
'water_subscription_number': well.water_subscription_number if well else '', 'phone2': mark_safe(f"<span class=\"fw-bold\">{profile.phone_number_2 if profile else ''}</span>"),
'electricity_subscription_number': well.electricity_subscription_number if well else '', 'water_subscription_number': mark_safe(f"<span class=\"fw-bold\">{well.water_subscription_number if well else ''}</span>"),
'water_meter_serial_number': well.water_meter_serial_number if well else '', 'electricity_subscription_number': mark_safe(f"<span class=\"fw-bold\">{well.electricity_subscription_number if well else ''}</span>"),
'well_power': well.well_power if well else '', 'water_meter_serial_number': mark_safe(f"<span class=\"fw-bold\">{well.water_meter_serial_number if well else ''}</span>"),
'request_code': instance.code, 'well_power': mark_safe(f"<span class=\"fw-bold\">{well.well_power if well else ''}</span>"),
'today': jalali_converter2(timezone.now()), 'request_code': mark_safe(f"<span class=\"fw-bold\">{instance.code}</span>"),
'today': mark_safe(f"<span class=\"fw-bold\">{jalali_converter2(timezone.now())}</span>"),
'company_name': mark_safe(f"<span class=\"fw-bold\">{instance.broker.company.name if instance.broker and instance.broker.company else ''}</span>"),
'city_name': mark_safe(f"<span class=\"fw-bold\">{instance.broker.affairs.county.city.name if instance.broker and instance.broker.affairs and instance.broker.affairs.county and instance.broker.affairs.county.city else ''}</span>"),
'card_number': mark_safe(f"<span class=\"fw-bold\">{instance.representative.profile.card_number if instance.representative else ''}</span>"),
'account_number': mark_safe(f"<span class=\"fw-bold\">{instance.representative.profile.account_number if instance.representative else ''}</span>"),
'bank_name': mark_safe(f"<span class=\"fw-bold\">{instance.representative.profile.get_bank_name_display() if instance.representative else ''}</span>"),
'prepayment_amount': mark_safe(f"<span class=\"fw-bold\">{int(total_paid):,}</span>"),
'prepayment_date': mark_safe(f"<span class=\"fw-bold\">{jalali_converter2(latest_payment_date)}</span>") if latest_payment_date else '',
} }
@ -35,10 +57,7 @@ def contract_step(request, instance_id, step_id):
step = get_object_or_404(instance.process.steps, id=step_id) step = get_object_or_404(instance.process.steps, id=step_id)
previous_step = instance.process.steps.filter(order__lt=step.order).last() previous_step = instance.process.steps.filter(order__lt=step.order).last()
next_step = instance.process.steps.filter(order__gt=step.order).first() next_step = instance.process.steps.filter(order__gt=step.order).first()
# Access control:
# - INSTALLER: can open step but cannot view contract body (show inline message)
# - Others: can view
# - Only BROKER can submit/complete this step
profile = getattr(request.user, 'profile', None) profile = getattr(request.user, 'profile', None)
is_broker = False is_broker = False
can_view_contract_body = True can_view_contract_body = True
@ -72,7 +91,6 @@ def contract_step(request, instance_id, step_id):
# If user submits to go next, only broker can complete and go to next # If user submits to go next, only broker can complete and go to next
if request.method == 'POST': if request.method == 'POST':
if not is_broker: if not is_broker:
from django.http import JsonResponse
return JsonResponse({'success': False, 'message': 'شما مجوز تایید این مرحله را ندارید'}, status=403) return JsonResponse({'success': False, 'message': 'شما مجوز تایید این مرحله را ندارید'}, status=403)
StepInstance.objects.update_or_create( StepInstance.objects.update_or_create(
process_instance=instance, process_instance=instance,

Binary file not shown.

View file

@ -7,6 +7,7 @@ from decimal import Decimal
from django.utils import timezone from django.utils import timezone
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
from django.conf import settings from django.conf import settings
from _helpers.utils import jalali_converter2
User = get_user_model() User = get_user_model()
@ -372,3 +373,6 @@ class Payment(BaseModel):
except Exception: except Exception:
pass pass
return result return result
def jpayment_date(self):
return jalali_converter2(self.payment_date)

View file

@ -150,7 +150,7 @@
<tr> <tr>
<td>{% if p.direction == 'in' %}<span class="badge bg-success">دریافتی{% else %}<span class="badge bg-warning text-dark">پرداختی{% endif %}</span></td> <td>{% if p.direction == 'in' %}<span class="badge bg-success">دریافتی{% else %}<span class="badge bg-warning text-dark">پرداختی{% endif %}</span></td>
<td>{{ p.amount|floatformat:0|intcomma:False }} تومان</td> <td>{{ p.amount|floatformat:0|intcomma:False }} تومان</td>
<td>{{ p.payment_date|date:'Y/m/d' }}</td> <td>{{ p.jpayment_date }}</td>
<td>{{ p.get_payment_method_display }}</td> <td>{{ p.get_payment_method_display }}</td>
<td>{{ p.reference_number|default:'-' }}</td> <td>{{ p.reference_number|default:'-' }}</td>
<td> <td>
@ -316,11 +316,32 @@
(function initPersianDatePicker(){ (function initPersianDatePicker(){
if (window.$ && $.fn.persianDatepicker && $('#id_payment_date').length) { if (window.$ && $.fn.persianDatepicker && $('#id_payment_date').length) {
$('#id_payment_date').persianDatepicker({ $('#id_payment_date').persianDatepicker({
format: 'YYYY/MM/DD', initialValue: false, autoClose: true, persianDigit: false, observer: true, format: 'YYYY/MM/DD',
initialValue: false,
autoClose: true,
persianDigit: false,
observer: true,
calendar: { persian: { locale: 'fa', leapYearMode: 'astronomical' } }, calendar: { persian: { locale: 'fa', leapYearMode: 'astronomical' } },
onSelect: function(unix){ onSelect: function(unix){
const g = new window.persianDate(unix).toCalendar('gregorian').format('YYYY-MM-DD'); // تبدیل تاریخ شمسی به میلادی برای ارسال به سرور
$('#id_payment_date').attr('data-gregorian', g); const gregorianDate = new Date(unix);
const year = gregorianDate.getFullYear();
const month = String(gregorianDate.getMonth() + 1).padStart(2, '0');
const day = String(gregorianDate.getDate()).padStart(2, '0');
const gregorianDateString = `${year}-${month}-${day}`;
// نمایش تاریخ شمسی در فیلد
if (window.persianDate) {
const persianDate = new window.persianDate(unix);
const persianDateString = persianDate.format('YYYY/MM/DD');
$('#id_payment_date').val(persianDateString);
} else {
// اگر persianDate در دسترس نبود، تاریخ میلادی را نمایش بده
$('#id_payment_date').val(gregorianDateString);
}
// ذخیره تاریخ میلادی در فیلد مخفی برای ارسال به سرور
$('#id_payment_date').attr('data-gregorian', gregorianDateString);
} }
}); });
} }
@ -328,8 +349,14 @@
function buildForm(){ function buildForm(){
const fd = new FormData(document.getElementById('formFinalPayment')); const fd = new FormData(document.getElementById('formFinalPayment'));
const g = document.getElementById('id_payment_date').getAttribute('data-gregorian');
if (g) { fd.set('payment_date', g); } // تبدیل تاریخ شمسی به میلادی برای ارسال
const persianDateValue = $('#id_payment_date').val();
const gregorianDateValue = $('#id_payment_date').attr('data-gregorian');
if (persianDateValue && gregorianDateValue) {
fd.set('payment_date', gregorianDateValue);
}
return fd; return fd;
} }
(function(){ (function(){

View file

@ -164,7 +164,7 @@
{% for p in payments %} {% for p in payments %}
<tr> <tr>
<td>{{ p.amount|floatformat:0|intcomma:False }} تومان</td> <td>{{ p.amount|floatformat:0|intcomma:False }} تومان</td>
<td>{{ p.payment_date|date:'Y/m/d' }}</td> <td>{{ p.jpayment_date }}</td>
<td>{{ p.get_payment_method_display }}</td> <td>{{ p.get_payment_method_display }}</td>
<td>{{ p.reference_number|default:'-' }}</td> <td>{{ p.reference_number|default:'-' }}</td>
<td> <td>
@ -359,6 +359,13 @@
} }
const form = document.getElementById('formAddPayment'); const form = document.getElementById('formAddPayment');
const fd = buildFormData(form); const fd = buildFormData(form);
// تبدیل تاریخ شمسی به میلادی برای ارسال
const persianDateValue = $('#id_payment_date').val();
const gregorianDateValue = $('#id_payment_date').attr('data-gregorian');
if (persianDateValue && gregorianDateValue) {
fd.set('payment_date', gregorianDateValue);
}
fetch('{% url "invoices:add_quote_payment" instance.id step.id %}', { fetch('{% url "invoices:add_quote_payment" instance.id step.id %}', {
method: 'POST', method: 'POST',
body: fd body: fd
@ -422,18 +429,24 @@
observer: true, observer: true,
calendar: { persian: { locale: 'fa', leapYearMode: 'astronomical' } }, calendar: { persian: { locale: 'fa', leapYearMode: 'astronomical' } },
onSelect: function(unix) { onSelect: function(unix) {
// تبدیل تاریخ شمسی به میلادی برای ارسال به سرور
const gregorianDate = new Date(unix); const gregorianDate = new Date(unix);
const year = gregorianDate.getFullYear(); const year = gregorianDate.getFullYear();
const month = String(gregorianDate.getMonth() + 1).padStart(2, '0'); const month = String(gregorianDate.getMonth() + 1).padStart(2, '0');
const day = String(gregorianDate.getDate()).padStart(2, '0'); const day = String(gregorianDate.getDate()).padStart(2, '0');
const gregorianDateString = `${year}-${month}-${day}`; const gregorianDateString = `${year}-${month}-${day}`;
// نمایش تاریخ شمسی در فیلد
if (window.persianDate) { if (window.persianDate) {
const persianDate = new window.persianDate(unix); const persianDate = new window.persianDate(unix);
const persianDateString = persianDate.format('YYYY/MM/DD'); const persianDateString = persianDate.format('YYYY/MM/DD');
$('#id_payment_date').val(persianDateString); $('#id_payment_date').val(persianDateString);
} else { } else {
// اگر persianDate در دسترس نبود، تاریخ میلادی را نمایش بده
$('#id_payment_date').val(gregorianDateString); $('#id_payment_date').val(gregorianDateString);
} }
// ذخیره تاریخ میلادی در فیلد مخفی برای ارسال به سرور
$('#id_payment_date').attr('data-gregorian', gregorianDateString); $('#id_payment_date').attr('data-gregorian', gregorianDateString);
} }
}); });

View file

@ -323,7 +323,7 @@
{% else %} {% else %}
{% if next_step %} {% if next_step %}
<a href="{% url 'processes:step_detail' instance.id next_step.id %}" <a href="{% url 'processes:step_detail' instance.id next_step.id %}"
class="btn btn-label-primary"> class="btn btn-primary">
مرحله بعد مرحله بعد
<i class="bx bx-chevron-left bx-sm me-sm-n2"></i> <i class="bx bx-chevron-left bx-sm me-sm-n2"></i>
</a> </a>

View file

@ -149,7 +149,7 @@
{% endif %} {% endif %}
{% else %} {% else %}
{% if next_step %} {% if next_step %}
<a href="{% url 'processes:step_detail' instance.id next_step.id %}" class="btn btn-label-primary"> <a href="{% url 'processes:step_detail' instance.id next_step.id %}" class="btn btn-primary">
مرحله بعد مرحله بعد
<i class="bx bx-chevron-left bx-sm me-sm-n2"></i> <i class="bx bx-chevron-left bx-sm me-sm-n2"></i>
</a> </a>

View file

@ -172,18 +172,24 @@ def create_quote(request, instance_id, step_id):
quote.status = 'draft' quote.status = 'draft'
quote.save(update_fields=['status']) quote.save(update_fields=['status'])
if next_step: # Reset ALL subsequent completed steps to in_progress
next_step_instance = instance.step_instances.filter(step=next_step).first() subsequent_steps = instance.process.steps.filter(order__gt=step.order)
if next_step_instance and next_step_instance.status == 'completed': for subsequent_step in subsequent_steps:
next_step_instance.status = 'in_progress' subsequent_step_instance = instance.step_instances.filter(step=subsequent_step).first()
next_step_instance.completed_at = None if subsequent_step_instance and subsequent_step_instance.status == 'completed':
next_step_instance.save(update_fields=['status', 'completed_at']) # 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 # Clear previous approvals if the step requires re-approval
try: try:
next_step_instance.approvals.all().delete() subsequent_step_instance.approvals.all().delete()
except Exception: except Exception:
pass pass
# Set current step to the next step
if next_step:
instance.current_step = next_step instance.current_step = next_step
instance.save(update_fields=['current_step']) instance.save(update_fields=['current_step'])
@ -524,6 +530,26 @@ def add_quote_payment(request, instance_id, step_id):
si.approvals.all().delete() si.approvals.all().delete()
except Exception: except Exception:
pass 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 # If current step is ahead of this step, reset it back to this step
try: try:
if instance.current_step and instance.current_step.order > step.order: if instance.current_step and instance.current_step.order > step.order:
@ -572,6 +598,26 @@ def delete_quote_payment(request, instance_id, step_id, payment_id):
si.approvals.all().delete() si.approvals.all().delete()
except Exception: except Exception:
pass 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 # If current step is ahead of this step, reset it back to this step
try: try:
if instance.current_step and instance.current_step.order > step.order: if instance.current_step and instance.current_step.order > step.order:
@ -1000,6 +1046,25 @@ def add_final_payment(request, instance_id, step_id):
si.save() si.save()
except Exception: except Exception:
pass 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
return JsonResponse({ return JsonResponse({
'success': True, 'success': True,
'redirect': reverse('invoices:final_settlement_step', args=[instance.id, step_id]), 'redirect': reverse('invoices:final_settlement_step', args=[instance.id, step_id]),