Compare commits
No commits in common. "9815aa4c730d99a2b8ba98c28545572f67c133a5" and "916c66a281a4879fe7e90ba751e389ce7cb96fd3" have entirely different histories.
9815aa4c73
...
916c66a281
21 changed files with 71 additions and 471 deletions
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
|
|
@ -20,7 +20,7 @@ class InstallationReportForm(forms.ModelForm):
|
||||||
model = InstallationReport
|
model = InstallationReport
|
||||||
fields = [
|
fields = [
|
||||||
'visited_date', 'new_water_meter_serial', 'seal_number',
|
'visited_date', 'new_water_meter_serial', 'seal_number',
|
||||||
'utm_x', 'utm_y', 'meter_type', 'meter_size', 'meter_model',
|
'utm_x', 'utm_y', 'meter_type', 'meter_size',
|
||||||
'discharge_pipe_diameter', 'usage_type', 'exploitation_license_number',
|
'discharge_pipe_diameter', 'usage_type', 'exploitation_license_number',
|
||||||
'motor_power', 'pre_calibration_flow_rate', 'post_calibration_flow_rate',
|
'motor_power', 'pre_calibration_flow_rate', 'post_calibration_flow_rate',
|
||||||
'water_meter_manufacturer', 'sim_number', 'driving_force',
|
'water_meter_manufacturer', 'sim_number', 'driving_force',
|
||||||
|
|
@ -62,13 +62,6 @@ class InstallationReportForm(forms.ModelForm):
|
||||||
'meter_size': forms.TextInput(attrs={
|
'meter_size': forms.TextInput(attrs={
|
||||||
'class': 'form-control'
|
'class': 'form-control'
|
||||||
}),
|
}),
|
||||||
'meter_model': forms.Select(attrs={
|
|
||||||
'class': 'form-select'
|
|
||||||
}, choices=[
|
|
||||||
('', 'انتخاب کنید'),
|
|
||||||
('A', 'A'),
|
|
||||||
('B', 'B')
|
|
||||||
]),
|
|
||||||
'discharge_pipe_diameter': forms.NumberInput(attrs={
|
'discharge_pipe_diameter': forms.NumberInput(attrs={
|
||||||
'class': 'form-control',
|
'class': 'form-control',
|
||||||
'min': '0',
|
'min': '0',
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 5.2.4 on 2025-10-07 04:50
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('installations', '0006_alter_installationreport_exploitation_license_number'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='installationreport',
|
|
||||||
name='meter_model',
|
|
||||||
field=models.CharField(blank=True, choices=[('A', 'A'), ('B', 'B')], max_length=20, null=True, verbose_name='مدل کنتور'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -47,11 +47,6 @@ class InstallationReport(BaseModel):
|
||||||
('volumetric', 'حجمی'),
|
('volumetric', 'حجمی'),
|
||||||
]
|
]
|
||||||
meter_type = models.CharField(max_length=20, choices=METER_TYPE_CHOICES, null=True, blank=True, verbose_name='نوع کنتور')
|
meter_type = models.CharField(max_length=20, choices=METER_TYPE_CHOICES, null=True, blank=True, verbose_name='نوع کنتور')
|
||||||
METER_MODEL_CHOICES = [
|
|
||||||
('A', 'A'),
|
|
||||||
('B', 'B'),
|
|
||||||
]
|
|
||||||
meter_model = models.CharField(max_length=20, choices=METER_MODEL_CHOICES, null=True, blank=True, verbose_name='مدل کنتور')
|
|
||||||
meter_size = models.CharField(max_length=50, null=True, blank=True, verbose_name='سایز کنتور')
|
meter_size = models.CharField(max_length=50, null=True, blank=True, verbose_name='سایز کنتور')
|
||||||
discharge_pipe_diameter = models.PositiveIntegerField(null=True, blank=True, verbose_name='قطر لوله آبده (اینچ)')
|
discharge_pipe_diameter = models.PositiveIntegerField(null=True, blank=True, verbose_name='قطر لوله آبده (اینچ)')
|
||||||
USAGE_TYPE_CHOICES = [
|
USAGE_TYPE_CHOICES = [
|
||||||
|
|
|
||||||
|
|
@ -86,11 +86,7 @@
|
||||||
<p class="text-nowrap mb-2"><i class="bx bx-purchase-tag bx-sm me-2"></i>سریال جدید: {{ report.new_water_meter_serial|default:'-' }}</p>
|
<p class="text-nowrap mb-2"><i class="bx bx-purchase-tag bx-sm me-2"></i>سریال جدید: {{ report.new_water_meter_serial|default:'-' }}</p>
|
||||||
<p class="text-nowrap mb-2"><i class="bx bx-lock-alt bx-sm me-2"></i>شماره پلمپ: {{ report.seal_number|default:'-' }}</p>
|
<p class="text-nowrap mb-2"><i class="bx bx-lock-alt bx-sm me-2"></i>شماره پلمپ: {{ report.seal_number|default:'-' }}</p>
|
||||||
<p class="text-nowrap mb-2"><i class="bx bx-chip bx-sm me-2"></i>نوع کنتور: {{ report.get_meter_type_display|default:'-' }}</p>
|
<p class="text-nowrap mb-2"><i class="bx bx-chip bx-sm me-2"></i>نوع کنتور: {{ report.get_meter_type_display|default:'-' }}</p>
|
||||||
{% if report.meter_type == 'smart' %}
|
|
||||||
<p class="text-nowrap mb-2"><i class="bx bx-chip bx-sm me-2"></i>مدل کنتور: {{ report.get_meter_model_display|default:'-' }}</p>
|
|
||||||
{% else %}
|
|
||||||
<p class="text-nowrap mb-2"><i class="bx bx-ruler bx-sm me-2"></i>سایز کنتور: {{ report.meter_size|default:'-' }}</p>
|
<p class="text-nowrap mb-2"><i class="bx bx-ruler bx-sm me-2"></i>سایز کنتور: {{ report.meter_size|default:'-' }}</p>
|
||||||
{% endif %}
|
|
||||||
<p class="text-nowrap mb-2"><i class="bx bx-tachometer bx-sm me-2"></i>قطر لوله آبده (اینچ): {{ report.discharge_pipe_diameter|default:'-' }}</p>
|
<p class="text-nowrap mb-2"><i class="bx bx-tachometer bx-sm me-2"></i>قطر لوله آبده (اینچ): {{ report.discharge_pipe_diameter|default:'-' }}</p>
|
||||||
<p class="text-nowrap mb-2"><i class="bx bx-building bx-sm me-2"></i>سازنده کنتور: {{ report.water_meter_manufacturer|default:'-' }}</p>
|
<p class="text-nowrap mb-2"><i class="bx bx-building bx-sm me-2"></i>سازنده کنتور: {{ report.water_meter_manufacturer|default:'-' }}</p>
|
||||||
<p class="text-nowrap mb-2"><i class="bx bx-sim-card bx-sm me-2"></i>شماره سیمکارت: {{ report.sim_number|default:'-' }}</p>
|
<p class="text-nowrap mb-2"><i class="bx bx-sim-card bx-sm me-2"></i>شماره سیمکارت: {{ report.sim_number|default:'-' }}</p>
|
||||||
|
|
@ -283,20 +279,13 @@
|
||||||
<div class="invalid-feedback">{{ form.meter_type.errors.0 }}</div>
|
<div class="invalid-feedback">{{ form.meter_type.errors.0 }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3" id="meter_size_wrapper">
|
<div class="col-md-3">
|
||||||
{{ form.meter_size.label_tag }}
|
{{ form.meter_size.label_tag }}
|
||||||
{{ form.meter_size }}
|
{{ form.meter_size }}
|
||||||
{% if form.meter_size.errors %}
|
{% if form.meter_size.errors %}
|
||||||
<div class="invalid-feedback">{{ form.meter_size.errors.0 }}</div>
|
<div class="invalid-feedback">{{ form.meter_size.errors.0 }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3" id="meter_model_wrapper">
|
|
||||||
{{ form.meter_model.label_tag }}
|
|
||||||
{{ form.meter_model }}
|
|
||||||
{% if form.meter_model.errors %}
|
|
||||||
<div class="invalid-feedback">{{ form.meter_size.errors.0 }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
{{ form.discharge_pipe_diameter.label_tag }}
|
{{ form.discharge_pipe_diameter.label_tag }}
|
||||||
{{ form.discharge_pipe_diameter }}
|
{{ form.discharge_pipe_diameter }}
|
||||||
|
|
@ -340,7 +329,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
{{ form.water_meter_manufacturer.label_tag }}حجمی
|
{{ form.water_meter_manufacturer.label_tag }}
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
{{ form.water_meter_manufacturer }}
|
{{ form.water_meter_manufacturer }}
|
||||||
{{ form.new_manufacturer }}
|
{{ form.new_manufacturer }}
|
||||||
|
|
@ -446,7 +435,7 @@
|
||||||
{% if qi.item.description %}<small class="text-muted">{{ qi.item.description }}</small>{% endif %}
|
{% if qi.item.description %}<small class="text-muted">{{ qi.item.description }}</small>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ qi.unit_price|floatformat:0|intcomma:False }} ریال</td>
|
<td>{{ qi.unit_price|floatformat:0|intcomma:False }} تومان</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="text-muted">{% if removed_qty|get_item:qi.item.id %}{{ removed_qty|get_item:qi.item.id }}{% else %}{{ qi.quantity }}{% endif %}</span>
|
<span class="text-muted">{% if removed_qty|get_item:qi.item.id %}{{ removed_qty|get_item:qi.item.id }}{% else %}{{ qi.quantity }}{% endif %}</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -485,7 +474,7 @@
|
||||||
{% if it.description %}<small class="text-muted">{{ it.description }}</small>{% endif %}
|
{% if it.description %}<small class="text-muted">{{ it.description }}</small>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ it.unit_price|floatformat:0|intcomma:False }} ریال</td>
|
<td>{{ it.unit_price|floatformat:0|intcomma:False }} تومان</td>
|
||||||
<td>
|
<td>
|
||||||
{% with add_entry=added_map|get_item:it.id %}
|
{% with add_entry=added_map|get_item:it.id %}
|
||||||
<input class="form-control form-control-sm" type="number" min="1" name="add_{{ it.id }}_qty" value="{% if add_entry %}{{ add_entry.qty }}{% endif %}">
|
<input class="form-control form-control-sm" type="number" min="1" name="add_{{ it.id }}_qty" value="{% if add_entry %}{{ add_entry.qty }}{% endif %}">
|
||||||
|
|
@ -516,7 +505,7 @@
|
||||||
{% if user_is_installer %}
|
{% if user_is_installer %}
|
||||||
<button type="submit" class="btn btn-success" form="installation-report-form">ثبت گزارش</button>
|
<button type="submit" class="btn btn-success" form="installation-report-form">ثبت گزارش</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if next_step and not edit_mode %}
|
{% if next_step %}
|
||||||
<a href="{% url 'processes:step_detail' instance.id next_step.id %}" class="btn btn-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>
|
||||||
|
|
@ -770,47 +759,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic meter field visibility based on meter type
|
|
||||||
(function() {
|
|
||||||
const meterTypeSelect = document.getElementById('{{ form.meter_type.id_for_label }}');
|
|
||||||
const meterSizeWrapper = document.getElementById('meter_size_wrapper');
|
|
||||||
const meterModelWrapper = document.getElementById('meter_model_wrapper');
|
|
||||||
|
|
||||||
function updateMeterFields() {
|
|
||||||
if (!meterTypeSelect) return;
|
|
||||||
|
|
||||||
const selectedType = meterTypeSelect.value;
|
|
||||||
|
|
||||||
if (selectedType === 'smart') {
|
|
||||||
// Show meter_model, hide meter_size
|
|
||||||
meterModelWrapper.style.display = '';
|
|
||||||
meterSizeWrapper.style.display = 'none';
|
|
||||||
// Clear meter_size value when hidden
|
|
||||||
const meterSizeInput = meterSizeWrapper.querySelector('input, select');
|
|
||||||
if (meterSizeInput) meterSizeInput.value = '';
|
|
||||||
} else if (selectedType === 'volumetric') {
|
|
||||||
// Show meter_size, hide meter_model
|
|
||||||
meterSizeWrapper.style.display = '';
|
|
||||||
meterModelWrapper.style.display = 'none';
|
|
||||||
// Clear meter_model value when hidden
|
|
||||||
const meterModelSelect = meterModelWrapper.querySelector('select');
|
|
||||||
if (meterModelSelect) meterModelSelect.value = '';
|
|
||||||
} else {
|
|
||||||
// No selection: hide both
|
|
||||||
meterSizeWrapper.style.display = 'none';
|
|
||||||
meterModelWrapper.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initial update on page load
|
|
||||||
updateMeterFields();
|
|
||||||
|
|
||||||
// Update on change
|
|
||||||
if (meterTypeSelect) {
|
|
||||||
meterTypeSelect.addEventListener('change', updateMeterFields);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,13 +57,13 @@ class InvoiceAdmin(SimpleHistoryAdmin):
|
||||||
status_display.short_description = "وضعیت"
|
status_display.short_description = "وضعیت"
|
||||||
|
|
||||||
def paid_amount_display(self, obj):
|
def paid_amount_display(self, obj):
|
||||||
return f"{obj.get_paid_amount():,.0f} ریال"
|
return f"{obj.get_paid_amount():,.0f} تومان"
|
||||||
paid_amount_display.short_description = "مبلغ پرداخت شده"
|
paid_amount_display.short_description = "مبلغ پرداخت شده"
|
||||||
|
|
||||||
def remaining_amount_display(self, obj):
|
def remaining_amount_display(self, obj):
|
||||||
amount = obj.get_remaining_amount()
|
amount = obj.get_remaining_amount()
|
||||||
color = "green" if amount <= 0 else "red"
|
color = "green" if amount <= 0 else "red"
|
||||||
return format_html('<span style="color: {};">{:,.0f} ریال</span>', color, amount)
|
return format_html('<span style="color: {};">{:,.0f} تومان</span>', color, amount)
|
||||||
remaining_amount_display.short_description = "مبلغ باقیمانده"
|
remaining_amount_display.short_description = "مبلغ باقیمانده"
|
||||||
|
|
||||||
@admin.register(Payment)
|
@admin.register(Payment)
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class Item(NameSlugModel):
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name} - {self.unit_price} ریال"
|
return f"{self.name} - {self.unit_price} تومان"
|
||||||
|
|
||||||
class Quote(NameSlugModel):
|
class Quote(NameSlugModel):
|
||||||
"""مدل پیشفاکتور"""
|
"""مدل پیشفاکتور"""
|
||||||
|
|
@ -137,11 +137,11 @@ class Quote(NameSlugModel):
|
||||||
return '<span class="badge bg-{}">{}</span>'.format(color, self.get_status_display())
|
return '<span class="badge bg-{}">{}</span>'.format(color, self.get_status_display())
|
||||||
|
|
||||||
def get_paid_amount(self):
|
def get_paid_amount(self):
|
||||||
"""خالص پرداختی (دریافتی از مشتری منهای پرداختی به مشتری) برای این پیشفاکتور بر اساس پرداختهای فاکتور مرتبط"""
|
"""مبلغ پرداخت شده برای این پیشفاکتور بر اساس پرداختهای فاکتور مرتبط"""
|
||||||
invoice = Invoice.objects.filter(quote=self).first()
|
invoice = Invoice.objects.filter(quote=self).first()
|
||||||
if not invoice:
|
if not invoice:
|
||||||
return Decimal('0')
|
return Decimal('0')
|
||||||
return sum((p.amount if p.direction == 'in' else -p.amount) for p in invoice.payments.filter(is_deleted=False).all())
|
return sum(p.amount for p in invoice.payments.filter(is_deleted=False).all())
|
||||||
|
|
||||||
def get_remaining_amount(self):
|
def get_remaining_amount(self):
|
||||||
"""مبلغ باقیمانده بر اساس پرداختها"""
|
"""مبلغ باقیمانده بر اساس پرداختها"""
|
||||||
|
|
@ -151,15 +151,6 @@ class Quote(NameSlugModel):
|
||||||
remaining = Decimal('0')
|
remaining = Decimal('0')
|
||||||
return remaining
|
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):
|
class QuoteItem(BaseModel):
|
||||||
"""مدل آیتمهای پیشفاکتور"""
|
"""مدل آیتمهای پیشفاکتور"""
|
||||||
quote = models.ForeignKey(Quote, on_delete=models.CASCADE, related_name='items', verbose_name="پیشفاکتور")
|
quote = models.ForeignKey(Quote, on_delete=models.CASCADE, related_name='items', verbose_name="پیشفاکتور")
|
||||||
|
|
@ -300,15 +291,6 @@ class Invoice(NameSlugModel):
|
||||||
remaining = self.final_amount - paid
|
remaining = self.final_amount - paid
|
||||||
return remaining
|
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):
|
def get_status_display_with_color(self):
|
||||||
"""نمایش وضعیت با رنگ"""
|
"""نمایش وضعیت با رنگ"""
|
||||||
|
|
@ -383,7 +365,7 @@ class Payment(BaseModel):
|
||||||
ordering = ['-payment_date']
|
ordering = ['-payment_date']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"پرداخت {self.amount} ریال - {self.invoice.name}"
|
return f"پرداخت {self.amount} تومان - {self.invoice.name}"
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""بروزرسانی مبالغ فاکتور"""
|
"""بروزرسانی مبالغ فاکتور"""
|
||||||
|
|
|
||||||
|
|
@ -124,8 +124,8 @@
|
||||||
<th style="width: 30%">شرح کالا/خدمات</th>
|
<th style="width: 30%">شرح کالا/خدمات</th>
|
||||||
<th style="width: 30%">توضیحات</th>
|
<th style="width: 30%">توضیحات</th>
|
||||||
<th style="width: 10%">تعداد</th>
|
<th style="width: 10%">تعداد</th>
|
||||||
<th style="width: 12.5%">قیمت واحد(ریال)</th>
|
<th style="width: 12.5%">قیمت واحد(تومان)</th>
|
||||||
<th style="width: 12.5%">قیمت کل(ریال)</th>
|
<th style="width: 12.5%">قیمت کل(تومان)</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
@ -144,29 +144,25 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<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.total_amount|floatformat:0|intcomma:False }}</strong></td>
|
<td><strong>{{ invoice.total_amount|floatformat:0|intcomma:False }}</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if invoice.discount_amount > 0 %}
|
{% if invoice.discount_amount > 0 %}
|
||||||
<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.discount_amount|floatformat:0|intcomma:False }}</strong></td>
|
<td><strong>{{ invoice.discount_amount|floatformat:0|intcomma:False }}</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% 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">
|
<tr class="total-section border-top border-2">
|
||||||
<td colspan="5" class="text-end"><strong>مبلغ نهایی (شامل مالیات)(ریال):</strong></td>
|
<td colspan="5" class="text-end"><strong>مبلغ نهایی (شامل مالیات)(تومان):</strong></td>
|
||||||
<td><strong>{{ invoice.final_amount|floatformat:0|intcomma:False }}</strong></td>
|
<td><strong>{{ invoice.final_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.get_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.get_remaining_amount|floatformat:0|intcomma:False }}</strong></td>
|
<td><strong>{{ invoice.get_remaining_amount|floatformat:0|intcomma:False }}</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
|
|
|
||||||
|
|
@ -68,19 +68,19 @@
|
||||||
<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">{{ invoice.final_amount|floatformat:0|intcomma:False }} ریال</div>
|
<div class="h5 mt-1">{{ invoice.final_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 text-success">{{ invoice.get_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.get_remaining_amount <= 0 %}text-success{% else %}text-danger{% endif %}">{{ invoice.get_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">
|
||||||
|
|
@ -100,8 +100,8 @@
|
||||||
<th class="text-center">افزوده</th>
|
<th class="text-center">افزوده</th>
|
||||||
<th class="text-center">حذف</th>
|
<th class="text-center">حذف</th>
|
||||||
<th class="text-center">تعداد نهایی</th>
|
<th class="text-center">تعداد نهایی</th>
|
||||||
<th class="text-end">قیمت واحد (ریال)</th>
|
<th class="text-end">قیمت واحد (تومان)</th>
|
||||||
<th class="text-end">قیمت کل (ریال)</th>
|
<th class="text-end">قیمت کل (تومان)</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
@ -153,27 +153,23 @@
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="6" class="text-end">مبلغ کل</th>
|
<th colspan="6" class="text-end">مبلغ کل</th>
|
||||||
<th class="text-end">{{ invoice.total_amount|floatformat:0|intcomma:False }} ریال</th>
|
<th class="text-end">{{ invoice.total_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">{{ invoice.discount_amount|floatformat:0|intcomma:False }} ریال</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>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="6" class="text-end">مبلغ نهایی (با مالیات)</th>
|
<th colspan="6" class="text-end">مبلغ نهایی (با مالیات)</th>
|
||||||
<th class="text-end">{{ invoice.final_amount|floatformat:0|intcomma:False }} ریال</th>
|
<th class="text-end">{{ invoice.final_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">{{ invoice.get_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.get_remaining_amount <= 0 %}text-success{% else %}text-danger{% endif %}">{{ invoice.get_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>
|
||||||
|
|
@ -227,8 +223,8 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">مبلغ (ریال)</label>
|
<label class="form-label">مبلغ (تومان)</label>
|
||||||
<input type="text" inputmode="numeric" pattern="\d*" class="form-control" name="amount" id="id_charge_amount" dir="ltr" autocomplete="off" required>
|
<input type="number" class="form-control" name="amount" id="id_charge_amount" min="1" required>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -250,17 +246,8 @@
|
||||||
else { el.classList.add('show'); el.style.display = 'block'; }
|
else { el.classList.add('show'); el.style.display = 'block'; }
|
||||||
}
|
}
|
||||||
function submitSpecialCharge(){
|
function submitSpecialCharge(){
|
||||||
const form = document.getElementById('specialChargeForm');
|
const fd = new FormData(document.getElementById('specialChargeForm'));
|
||||||
const fd = new FormData(form);
|
|
||||||
fd.append('csrfmiddlewaretoken', document.querySelector('input[name=csrfmiddlewaretoken]').value);
|
fd.append('csrfmiddlewaretoken', document.querySelector('input[name=csrfmiddlewaretoken]').value);
|
||||||
// Ensure raw numeric amount is sent
|
|
||||||
(function ensureRawAmount(){
|
|
||||||
const amountInput = document.getElementById('id_charge_amount');
|
|
||||||
if (amountInput){
|
|
||||||
const raw = (amountInput.getAttribute('data-raw-value') || amountInput.value.replace(/\D/g, ''));
|
|
||||||
if (raw) fd.set('amount', raw);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
fetch('{% url "invoices:add_special_charge" instance.id step.id %}', { method: 'POST', body: fd })
|
fetch('{% url "invoices:add_special_charge" instance.id step.id %}', { method: 'POST', body: fd })
|
||||||
.then(r=>r.json()).then(resp=>{
|
.then(r=>r.json()).then(resp=>{
|
||||||
if (resp.success){
|
if (resp.success){
|
||||||
|
|
@ -298,8 +285,6 @@
|
||||||
}
|
}
|
||||||
}).catch(()=> showToast('خطا در ارتباط با سرور', 'danger'));
|
}).catch(()=> showToast('خطا در ارتباط با سرور', 'danger'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Number formatting is handled by number-formatter.js
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@
|
||||||
<div class="bs-stepper-content">
|
<div class="bs-stepper-content">
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
{% if is_broker and invoice.get_remaining_amount != 0 %}
|
{% if is_broker %}
|
||||||
<div class="col-12 col-lg-5">
|
<div class="col-12 col-lg-5">
|
||||||
<div class="card border h-100">
|
<div class="card border h-100">
|
||||||
<div class="card-header"><h5 class="mb-0">ثبت تراکنش تسویه</h5></div>
|
<div class="card-header"><h5 class="mb-0">ثبت تراکنش تسویه</h5></div>
|
||||||
|
|
@ -75,8 +75,8 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">مبلغ (ریال)</label>
|
<label class="form-label">مبلغ (تومان)</label>
|
||||||
<input type="text" inputmode="numeric" pattern="\d*" class="form-control" name="amount" id="id_amount" dir="ltr" autocomplete="off" required>
|
<input type="number" min="1" class="form-control" name="amount" id="id_amount" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">تاریخ</label>
|
<label class="form-label">تاریخ</label>
|
||||||
|
|
@ -122,19 +122,19 @@
|
||||||
<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">{{ invoice.final_amount|floatformat:0|intcomma:False }} ریال</div>
|
<div class="h5 mt-1">{{ invoice.final_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 text-success">{{ invoice.get_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.get_remaining_amount <= 0 %}text-success{% else %}text-danger{% endif %}">{{ invoice.get_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">
|
||||||
|
|
@ -166,7 +166,7 @@
|
||||||
{% for p in payments %}
|
{% for p in payments %}
|
||||||
<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.jpayment_date }}</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>
|
||||||
|
|
@ -193,7 +193,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if approver_statuses and invoice.get_remaining_amount != 0 and step_instance.status != 'completed' %}
|
{% if approver_statuses %}
|
||||||
<div class="card border mt-2">
|
<div class="card border mt-2">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h6 class="mb-0">وضعیت تاییدها</h6>
|
<h6 class="mb-0">وضعیت تاییدها</h6>
|
||||||
|
|
@ -320,7 +320,7 @@
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
{% if invoice.get_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.get_remaining_amount|floatformat:0|intcomma:False }} ریال</strong><br>
|
مانده فاکتور: <strong>{{ invoice.get_remaining_amount|floatformat:0|intcomma:False }} تومان</strong><br>
|
||||||
امکان تایید تا تسویه کامل فاکتور وجود ندارد.
|
امکان تایید تا تسویه کامل فاکتور وجود ندارد.
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
@ -405,14 +405,6 @@
|
||||||
|
|
||||||
function buildForm(){
|
function buildForm(){
|
||||||
const fd = new FormData(document.getElementById('formFinalPayment'));
|
const fd = new FormData(document.getElementById('formFinalPayment'));
|
||||||
// Ensure raw numeric amount is sent
|
|
||||||
(function ensureRawAmount(){
|
|
||||||
const amountInput = document.getElementById('id_amount');
|
|
||||||
if (amountInput){
|
|
||||||
const raw = (amountInput.getAttribute('data-raw-value') || amountInput.value.replace(/\D/g, ''));
|
|
||||||
if (raw) fd.set('amount', raw);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
// تبدیل تاریخ شمسی به میلادی برای ارسال
|
// تبدیل تاریخ شمسی به میلادی برای ارسال
|
||||||
const persianDateValue = $('#id_payment_date').val();
|
const persianDateValue = $('#id_payment_date').val();
|
||||||
|
|
@ -473,24 +465,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy approve button removed; using modal forms below
|
// Legacy approve button removed; using modal forms below
|
||||||
|
|
||||||
// Handle AJAX form submission with number formatting
|
|
||||||
$(document).ready(function() {
|
|
||||||
// Override buildForm function for AJAX submission
|
|
||||||
const originalBuildForm = window.buildForm;
|
|
||||||
window.buildForm = function() {
|
|
||||||
// Set raw values before creating FormData
|
|
||||||
if (window.setRawValuesForSubmission) {
|
|
||||||
window.setRawValuesForSubmission();
|
|
||||||
}
|
|
||||||
const result = originalBuildForm ? originalBuildForm() : new FormData(document.querySelector('form'));
|
|
||||||
// Restore formatted values for display
|
|
||||||
if (window.restoreFormattedValues) {
|
|
||||||
window.restoreFormattedValues();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">مبلغ (ریال)</label>
|
<label class="form-label">مبلغ (تومان)</label>
|
||||||
<input type="text" inputmode="numeric" pattern="\d*" class="form-control" name="amount" id="id_amount" dir="ltr" autocomplete="off" required>
|
<input type="number" min="1" class="form-control" name="amount" id="id_amount" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">تاریخ پرداخت/سررسید چک</label>
|
<label class="form-label">تاریخ پرداخت/سررسید چک</label>
|
||||||
|
|
@ -117,19 +117,19 @@
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<div class="border rounded p-3">
|
<div class="border rounded p-3">
|
||||||
<div class="small text-muted">مبلغ نهایی پیشفاکتور (با مالیات)</div>
|
<div class="small text-muted">مبلغ نهایی پیشفاکتور (با مالیات)</div>
|
||||||
<div class="h5 mt-1">{{ totals.final_amount|floatformat:0|intcomma:False }} ریال</div>
|
<div class="h5 mt-1">{{ totals.final_amount|floatformat:0|intcomma:False }} تومان</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<div class="border rounded p-3">
|
<div class="border rounded p-3">
|
||||||
<div class="small text-muted">مبلغ پرداختشده</div>
|
<div class="small text-muted">مبلغ پرداختشده</div>
|
||||||
<div class="h5 mt-1 text-success">{{ totals.paid_amount|floatformat:0|intcomma:False }} ریال</div>
|
<div class="h5 mt-1 text-success">{{ totals.paid_amount|floatformat:0|intcomma:False }} تومان</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<div class="border rounded p-3">
|
<div class="border rounded p-3">
|
||||||
<div class="small text-muted">مانده</div>
|
<div class="small text-muted">مانده</div>
|
||||||
<div class="h5 mt-1 {% if totals.is_fully_paid %}text-success{% else %}text-danger{% endif %}">{{ totals.remaining_amount|floatformat:0|intcomma:False }} ریال</div>
|
<div class="h5 mt-1 {% if totals.is_fully_paid %}text-success{% else %}text-danger{% endif %}">{{ totals.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">
|
||||||
|
|
@ -153,7 +153,6 @@
|
||||||
<table class="table table-striped mb-0">
|
<table class="table table-striped mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>نوع</th>
|
|
||||||
<th>مبلغ</th>
|
<th>مبلغ</th>
|
||||||
<th>تاریخ پرداخت/سررسید چک</th>
|
<th>تاریخ پرداخت/سررسید چک</th>
|
||||||
<th>روش</th>
|
<th>روش</th>
|
||||||
|
|
@ -164,8 +163,7 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for p in payments %}
|
{% for p in payments %}
|
||||||
<tr>
|
<tr>
|
||||||
<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 class="{% if p.direction == 'in' %}text-success{% else %}text-danger{% endif %}">{{ p.amount|floatformat:0|intcomma:False }} ریال</td>
|
|
||||||
<td>{{ p.jpayment_date }}</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>
|
||||||
|
|
@ -177,7 +175,9 @@
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_broker %}
|
{% if is_broker %}
|
||||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="openDeleteModal('{{ p.id }}')" title="حذف" aria-label="حذف"><i class="bx bx-trash"></i></button>
|
<button type="button" class="btn btn-sm btn-outline-danger" onclick="openDeleteModal('{{ p.id }}')" title="حذف" aria-label="حذف">
|
||||||
|
<i class="bx bx-trash"></i>
|
||||||
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -301,7 +301,7 @@
|
||||||
{% if not totals.is_fully_paid %}
|
{% if not totals.is_fully_paid %}
|
||||||
<div class="alert alert-warning" role="alert">
|
<div class="alert alert-warning" role="alert">
|
||||||
مبلغی از پیشفاکتور هنوز پرداخت نشده است.
|
مبلغی از پیشفاکتور هنوز پرداخت نشده است.
|
||||||
<div class="mt-1">مانده: <strong>{{ totals.remaining_amount|floatformat:0|intcomma:False }} ریال</strong></div>
|
<div class="mt-1">مانده: <strong>{{ totals.remaining_amount|floatformat:0|intcomma:False }} تومان</strong></div>
|
||||||
</div>
|
</div>
|
||||||
آیا مطمئن هستید که میخواهید مرحله را تایید کنید؟
|
آیا مطمئن هستید که میخواهید مرحله را تایید کنید؟
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
@ -366,12 +366,6 @@
|
||||||
}
|
}
|
||||||
const form = document.getElementById('formAddPayment');
|
const form = document.getElementById('formAddPayment');
|
||||||
const fd = buildFormData(form);
|
const fd = buildFormData(form);
|
||||||
// Ensure raw numeric amount is sent
|
|
||||||
(function ensureRawAmount(){
|
|
||||||
const amountInput = document.getElementById('id_amount');
|
|
||||||
const raw = (amountInput.getAttribute('data-raw-value') || amountInput.value.replace(/\D/g, ''));
|
|
||||||
if (raw) fd.set('amount', raw);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// تبدیل تاریخ شمسی به میلادی برای ارسال
|
// تبدیل تاریخ شمسی به میلادی برای ارسال
|
||||||
const persianDateValue = $('#id_payment_date').val();
|
const persianDateValue = $('#id_payment_date').val();
|
||||||
|
|
@ -389,7 +383,7 @@
|
||||||
setTimeout(() => { window.location.href = resp.redirect; }, 700);
|
setTimeout(() => { window.location.href = resp.redirect; }, 700);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showToast((resp.message || resp.error || 'خطا در ثبت فیش'), 'danger');
|
showToast(resp.message + ':' + resp.error || 'خطا در ثبت فیش', 'danger');
|
||||||
}
|
}
|
||||||
}).catch(() => showToast('خطا در ارتباط با سرور', 'danger'));
|
}).catch(() => showToast('خطا در ارتباط با سرور', 'danger'));
|
||||||
});
|
});
|
||||||
|
|
@ -466,7 +460,6 @@
|
||||||
} catch (e) { console.error('Error initializing Persian Date Picker:', e); }
|
} catch (e) { console.error('Error initializing Persian Date Picker:', e); }
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -200,9 +200,9 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-nowrap">{{ quote_item.item.name }}</td>
|
<td class="text-nowrap">{{ quote_item.item.name }}</td>
|
||||||
<td class="text-nowrap">{{ quote_item.item.description|default:"-" }}</td>
|
<td class="text-nowrap">{{ quote_item.item.description|default:"-" }}</td>
|
||||||
<td>{{ quote_item.unit_price|floatformat:0|intcomma:False }} ریال</td>
|
<td>{{ quote_item.unit_price|floatformat:0|intcomma:False }} تومان</td>
|
||||||
<td>{{ quote_item.quantity }}</td>
|
<td>{{ quote_item.quantity }}</td>
|
||||||
<td>{{ quote_item.total_price|floatformat:0|intcomma:False }} ریال</td>
|
<td>{{ quote_item.total_price|floatformat:0|intcomma:False }} تومان</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<tr>
|
<tr>
|
||||||
|
|
@ -213,16 +213,14 @@
|
||||||
{% if quote.discount_amount > 0 %}
|
{% if quote.discount_amount > 0 %}
|
||||||
<p class="mb-2">تخفیف:</p>
|
<p class="mb-2">تخفیف:</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="mb-2">مالیات بر ارزش افزوده:</p>
|
|
||||||
<p class="mb-0 fw-bold">مبلغ نهایی (شامل مالیات):</p>
|
<p class="mb-0 fw-bold">مبلغ نهایی (شامل مالیات):</p>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-5">
|
<td class="px-4 py-5">
|
||||||
<p class="fw-medium mb-2">{{ quote.total_amount|floatformat:0|intcomma:False }} ریال</p>
|
<p class="fw-medium mb-2">{{ quote.total_amount|floatformat:0|intcomma:False }} تومان</p>
|
||||||
{% if quote.discount_amount > 0 %}
|
{% if quote.discount_amount > 0 %}
|
||||||
<p class="fw-medium mb-2">{{ quote.discount_amount|floatformat:0|intcomma:False }} ریال</p>
|
<p class="fw-medium mb-2">{{ quote.discount_amount|floatformat:0|intcomma:False }} تومان</p>
|
||||||
{% endif %}
|
{% 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>
|
||||||
<p class="fw-bold mb-0">{{ quote.final_amount|floatformat:0|intcomma:False }} ریال</p>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
||||||
|
|
@ -185,8 +185,8 @@
|
||||||
<th style="width: 30%">شرح کالا/خدمات</th>
|
<th style="width: 30%">شرح کالا/خدمات</th>
|
||||||
<th style="width: 30%">توضیحات</th>
|
<th style="width: 30%">توضیحات</th>
|
||||||
<th style="width: 10%">تعداد</th>
|
<th style="width: 10%">تعداد</th>
|
||||||
<th style="width: 12.5%">قیمت واحد(ریال)</th>
|
<th style="width: 12.5%">قیمت واحد(تومان)</th>
|
||||||
<th style="width: 12.5%">قیمت کل(ریال)</th>
|
<th style="width: 12.5%">قیمت کل(تومان)</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
@ -203,21 +203,17 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<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>{{ quote.total_amount|floatformat:0|intcomma:False }}</strong></td>
|
<td><strong>{{ quote.total_amount|floatformat:0|intcomma:False }}</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if quote.discount_amount > 0 %}
|
{% if quote.discount_amount > 0 %}
|
||||||
<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>{{ quote.discount_amount|floatformat:0|intcomma:False }}</strong></td>
|
<td><strong>{{ quote.discount_amount|floatformat:0|intcomma:False }}</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% 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">
|
<tr class="total-section border-top border-2">
|
||||||
<td colspan="5" class="text-end"><strong>مبلغ نهایی (با مالیات)(ریال):</strong></td>
|
<td colspan="5" class="text-end"><strong>مبلغ نهایی (با مالیات)(تومان):</strong></td>
|
||||||
<td><strong>{{ quote.final_amount|floatformat:0|intcomma:False }}</strong></td>
|
<td><strong>{{ quote.final_amount|floatformat:0|intcomma:False }}</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<h6>پیشفاکتور موجود</h6>
|
<h6>پیشفاکتور موجود</h6>
|
||||||
<span class="mb-1">{{ existing_quote.name }} | </span>
|
<span class="mb-1">{{ existing_quote.name }} | </span>
|
||||||
<span class="mb-1">مبلغ کل (با احتساب مالیات): {{ existing_quote.final_amount|floatformat:0|intcomma:False }} ریال | </span>
|
<span class="mb-1">مبلغ کل (با احتساب مالیات): {{ existing_quote.final_amount|floatformat:0|intcomma:False }} تومان | </span>
|
||||||
<span class="mb-0">وضعیت: {{ existing_quote.get_status_display_with_color|safe }}</span>
|
<span class="mb-0">وضعیت: {{ existing_quote.get_status_display_with_color|safe }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -97,7 +97,7 @@
|
||||||
{% if item.description %}<small class="text-muted">{{ item.description }}</small>{% endif %}
|
{% if item.description %}<small class="text-muted">{{ item.description }}</small>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ item.unit_price|floatformat:0|intcomma:False }} ریال</td>
|
<td>{{ item.unit_price|floatformat:0|intcomma:False }} تومان</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="number" class="form-control form-control-sm quote-item-qty" min="1"
|
<input type="number" class="form-control form-control-sm quote-item-qty" min="1"
|
||||||
data-item-id="{{ item.id }}"
|
data-item-id="{{ item.id }}"
|
||||||
|
|
|
||||||
|
|
@ -898,29 +898,6 @@ def add_special_charge(request, instance_id, step_id):
|
||||||
unit_price=amount_dec,
|
unit_price=amount_dec,
|
||||||
)
|
)
|
||||||
invoice.calculate_totals()
|
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])})
|
return JsonResponse({'success': True, 'redirect': reverse('invoices:final_invoice_step', args=[instance.id, step_id])})
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -944,29 +921,6 @@ def delete_special_charge(request, instance_id, step_id, item_id):
|
||||||
return JsonResponse({'success': False, 'message': 'امکان حذف این مورد وجود ندارد'})
|
return JsonResponse({'success': False, 'message': 'امکان حذف این مورد وجود ندارد'})
|
||||||
inv_item.hard_delete()
|
inv_item.hard_delete()
|
||||||
invoice.calculate_totals()
|
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])})
|
return JsonResponse({'success': True, 'redirect': reverse('invoices:final_invoice_step', args=[instance.id, step_id])})
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -986,23 +940,6 @@ def final_settlement_step(request, instance_id, step_id):
|
||||||
# Ensure step instance exists
|
# Ensure step instance exists
|
||||||
step_instance, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step, defaults={'status': 'in_progress'})
|
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)
|
# 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', 'approved_by').filter(is_deleted=False))
|
approvals = list(step_instance.approvals.select_related('role', 'approved_by').filter(is_deleted=False))
|
||||||
|
|
@ -1111,14 +1048,6 @@ def final_settlement_step(request, instance_id, step_id):
|
||||||
except Exception:
|
except Exception:
|
||||||
messages.error(request, 'فقط مدیر مجاز به تایید اضطراری است.')
|
messages.error(request, 'فقط مدیر مجاز به تایید اضطراری است.')
|
||||||
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)
|
||||||
# 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
|
# Mark step completed regardless of remaining amount/approvals
|
||||||
step_instance.status = 'approved'
|
step_instance.status = 'approved'
|
||||||
step_instance.save()
|
step_instance.save()
|
||||||
|
|
@ -1165,14 +1094,6 @@ def add_final_payment(request, instance_id, step_id):
|
||||||
except Exception:
|
except Exception:
|
||||||
return JsonResponse({'success': False, 'message': 'شما مجوز افزودن تراکنش تسویه را ندارید'}, status=403)
|
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()
|
amount = (request.POST.get('amount') or '').strip()
|
||||||
payment_date = (request.POST.get('payment_date') or '').strip()
|
payment_date = (request.POST.get('payment_date') or '').strip()
|
||||||
payment_method = (request.POST.get('payment_method') or '').strip()
|
payment_method = (request.POST.get('payment_method') or '').strip()
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,9 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% 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.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 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.get_remaining_amount <= 0 %}text-success{% else %}text-danger{% endif %}">{{ invoice.get_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">
|
||||||
|
|
@ -237,7 +237,7 @@
|
||||||
{% for p in payments %}
|
{% for p in payments %}
|
||||||
<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.payment_date|date:'Y/m/d' }}</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>
|
||||||
|
|
|
||||||
|
|
@ -245,12 +245,7 @@
|
||||||
<small class="text-muted">{{ item.progress_percentage }}%</small>
|
<small class="text-muted">{{ item.progress_percentage }}%</small>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>{{ item.instance.get_status_display_with_color|safe }}</td>
|
||||||
{{ item.instance.get_status_display_with_color|safe }}
|
|
||||||
{% if item.emergency_approved %}
|
|
||||||
<span class="badge bg-warning text-dark ms-1" title="تایید اضطراری">تایید اضطراری</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
{% if item.installation_scheduled_date %}
|
{% if item.installation_scheduled_date %}
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -123,17 +123,6 @@ def request_list(request):
|
||||||
reference_date = None
|
reference_date = None
|
||||||
|
|
||||||
installation_scheduled_date = reference_date if reference_date and reference_date > sched_date else sched_date
|
installation_scheduled_date = reference_date if reference_date and reference_date > sched_date else sched_date
|
||||||
|
|
||||||
# Emergency approved flag (final settlement step forced approval)
|
|
||||||
try:
|
|
||||||
final_settlement_step = instance.process.steps.filter(order=8).first()
|
|
||||||
emergency_approved = False
|
|
||||||
if final_settlement_step:
|
|
||||||
si = instance.step_instances.filter(step=final_settlement_step).first()
|
|
||||||
emergency_approved = bool(si and si.status == 'approved')
|
|
||||||
except Exception:
|
|
||||||
emergency_approved = False
|
|
||||||
|
|
||||||
instances_with_progress.append({
|
instances_with_progress.append({
|
||||||
'instance': instance,
|
'instance': instance,
|
||||||
'progress_percentage': round(progress_percentage),
|
'progress_percentage': round(progress_percentage),
|
||||||
|
|
@ -141,7 +130,6 @@ def request_list(request):
|
||||||
'total_steps': total_steps,
|
'total_steps': total_steps,
|
||||||
'installation_scheduled_date': installation_scheduled_date,
|
'installation_scheduled_date': installation_scheduled_date,
|
||||||
'installation_overdue_days': overdue_days,
|
'installation_overdue_days': overdue_days,
|
||||||
'emergency_approved': emergency_approved,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Summary stats for header cards
|
# Summary stats for header cards
|
||||||
|
|
|
||||||
|
|
@ -1,144 +0,0 @@
|
||||||
/**
|
|
||||||
* Number Formatter Utility
|
|
||||||
* Formats numbers with comma separators for better readability
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Format number with comma separators (e.g., 1234567 -> 1,234,567)
|
|
||||||
function formatNumber(num) {
|
|
||||||
if (!num) return '';
|
|
||||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove comma separators from formatted number
|
|
||||||
function unformatNumber(str) {
|
|
||||||
if (!str) return '';
|
|
||||||
return str.replace(/,/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract only digits from any string
|
|
||||||
function extractDigits(str) {
|
|
||||||
if (!str) return '';
|
|
||||||
return str.replace(/\D/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize number formatting for specified input selectors
|
|
||||||
function initNumberFormatting(selectors) {
|
|
||||||
if (typeof $ === 'undefined') {
|
|
||||||
console.warn('jQuery not found. Number formatting requires jQuery.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).ready(function() {
|
|
||||||
selectors.forEach(function(selector) {
|
|
||||||
// Store cursor position to maintain it after formatting
|
|
||||||
function setCursorPosition(input, pos) {
|
|
||||||
if (input.setSelectionRange) {
|
|
||||||
input.setSelectionRange(pos, pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$(selector).on('input', function(e) {
|
|
||||||
let input = $(this);
|
|
||||||
let inputElement = this;
|
|
||||||
let value = input.val();
|
|
||||||
let cursorPos = inputElement.selectionStart;
|
|
||||||
|
|
||||||
// Extract only digits
|
|
||||||
let digitsOnly = extractDigits(value);
|
|
||||||
|
|
||||||
// Store raw value
|
|
||||||
input.attr('data-raw-value', digitsOnly);
|
|
||||||
|
|
||||||
// Format and set the value
|
|
||||||
let formattedValue = formatNumber(digitsOnly);
|
|
||||||
input.val(formattedValue);
|
|
||||||
|
|
||||||
// Adjust cursor position
|
|
||||||
let oldLength = value.length;
|
|
||||||
let newLength = formattedValue.length;
|
|
||||||
let newCursorPos = cursorPos + (newLength - oldLength);
|
|
||||||
|
|
||||||
// Make sure cursor position is valid
|
|
||||||
if (newCursorPos < 0) newCursorPos = 0;
|
|
||||||
if (newCursorPos > newLength) newCursorPos = newLength;
|
|
||||||
|
|
||||||
// Set cursor position after a short delay
|
|
||||||
setTimeout(function() {
|
|
||||||
setCursorPosition(inputElement, newCursorPos);
|
|
||||||
}, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle paste events
|
|
||||||
$(selector).on('paste', function(e) {
|
|
||||||
let input = $(this);
|
|
||||||
setTimeout(function() {
|
|
||||||
let value = input.val();
|
|
||||||
let digitsOnly = extractDigits(value);
|
|
||||||
input.attr('data-raw-value', digitsOnly);
|
|
||||||
input.val(formatNumber(digitsOnly));
|
|
||||||
}, 1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Before form submission, replace formatted values with raw values
|
|
||||||
$('form').on('submit', function() {
|
|
||||||
selectors.forEach(function(selector) {
|
|
||||||
let input = $(selector);
|
|
||||||
let rawValue = input.attr('data-raw-value');
|
|
||||||
if (rawValue) {
|
|
||||||
input.val(rawValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to get raw value from formatted input
|
|
||||||
function getRawValue(input) {
|
|
||||||
return $(input).attr('data-raw-value') || unformatNumber($(input).val());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to set raw value before AJAX submission
|
|
||||||
function setRawValuesForSubmission(selectors) {
|
|
||||||
selectors.forEach(function(selector) {
|
|
||||||
let input = $(selector);
|
|
||||||
let rawValue = input.attr('data-raw-value');
|
|
||||||
if (rawValue) {
|
|
||||||
input.val(rawValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to restore formatted values after AJAX submission
|
|
||||||
function restoreFormattedValues(selectors) {
|
|
||||||
selectors.forEach(function(selector) {
|
|
||||||
let input = $(selector);
|
|
||||||
let rawValue = input.attr('data-raw-value');
|
|
||||||
if (rawValue) {
|
|
||||||
input.val(formatNumber(rawValue));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-initialize for common amount input selectors
|
|
||||||
$(document).ready(function() {
|
|
||||||
const commonSelectors = [
|
|
||||||
'#id_amount',
|
|
||||||
'#id_charge_amount',
|
|
||||||
'input[name="amount"]',
|
|
||||||
'input[name="unit_price"]',
|
|
||||||
'input[name="price"]'
|
|
||||||
];
|
|
||||||
|
|
||||||
initNumberFormatting(commonSelectors);
|
|
||||||
|
|
||||||
// Make helper functions globally available for AJAX forms
|
|
||||||
window.formatNumber = formatNumber;
|
|
||||||
window.unformatNumber = unformatNumber;
|
|
||||||
window.getRawValue = getRawValue;
|
|
||||||
// Avoid name collision causing recursion by aliasing helpers
|
|
||||||
const __nf_setRawValuesForSubmission = setRawValuesForSubmission;
|
|
||||||
const __nf_restoreFormattedValues = restoreFormattedValues;
|
|
||||||
window.setRawValuesForSubmission = function() { __nf_setRawValuesForSubmission(commonSelectors); };
|
|
||||||
window.restoreFormattedValues = function() { __nf_restoreFormattedValues(commonSelectors); };
|
|
||||||
});
|
|
||||||
|
|
@ -169,8 +169,6 @@ layout-navbar-fixed layout-menu-fixed layout-compact
|
||||||
<!-- Main JS -->
|
<!-- Main JS -->
|
||||||
<script src="{% static 'assets/js/main.js' %}"></script>
|
<script src="{% static 'assets/js/main.js' %}"></script>
|
||||||
|
|
||||||
<!-- Number Formatter JS -->
|
|
||||||
<script src="{% static 'assets/js/number-formatter.js' %}"></script>
|
|
||||||
|
|
||||||
<!-- Page JS -->
|
<!-- Page JS -->
|
||||||
<script src="{% static 'assets/js/dashboards-analytics.js' %}"></script>
|
<script src="{% static 'assets/js/dashboards-analytics.js' %}"></script>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue