Merge remote-tracking branch 'origin' into shafafiyat/production
This commit is contained in:
		
						commit
						2a59c1acb5
					
				
					 20 changed files with 477 additions and 71 deletions
				
			
		
							
								
								
									
										4
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -9,8 +9,8 @@
 | 
			
		|||
*.pyc
 | 
			
		||||
__pycache__/
 | 
			
		||||
local_settings.py
 | 
			
		||||
# *.sqlite3
 | 
			
		||||
# db.sqlite3
 | 
			
		||||
*.sqlite3
 | 
			
		||||
db.sqlite3
 | 
			
		||||
db.sqlite3-journal
 | 
			
		||||
media
 | 
			
		||||
#static
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ class InstallationReportForm(forms.ModelForm):
 | 
			
		|||
        model = InstallationReport
 | 
			
		||||
        fields = [
 | 
			
		||||
            'visited_date', 'new_water_meter_serial', 'seal_number',
 | 
			
		||||
            'utm_x', 'utm_y', 'meter_type', 'meter_size',
 | 
			
		||||
            'utm_x', 'utm_y', 'meter_type', 'meter_size', 'meter_model',
 | 
			
		||||
            'discharge_pipe_diameter', 'usage_type', 'exploitation_license_number',
 | 
			
		||||
            'motor_power', 'pre_calibration_flow_rate', 'post_calibration_flow_rate',
 | 
			
		||||
            'water_meter_manufacturer', 'sim_number', 'driving_force',
 | 
			
		||||
| 
						 | 
				
			
			@ -62,6 +62,13 @@ class InstallationReportForm(forms.ModelForm):
 | 
			
		|||
            'meter_size': forms.TextInput(attrs={
 | 
			
		||||
                'class': 'form-control'
 | 
			
		||||
            }),
 | 
			
		||||
            'meter_model': forms.Select(attrs={
 | 
			
		||||
                'class': 'form-select'
 | 
			
		||||
            }, choices=[
 | 
			
		||||
                ('', 'انتخاب کنید'),
 | 
			
		||||
                ('A', 'A'),
 | 
			
		||||
                ('B', 'B')
 | 
			
		||||
            ]),
 | 
			
		||||
            'discharge_pipe_diameter': forms.NumberInput(attrs={
 | 
			
		||||
                'class': 'form-control',
 | 
			
		||||
                'min': '0',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
# Generated by Django 5.2.5 on 2025-10-07 06: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,6 +47,11 @@ class InstallationReport(BaseModel):
 | 
			
		|||
        ('volumetric', 'حجمی'),
 | 
			
		||||
    ]
 | 
			
		||||
    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='سایز کنتور')
 | 
			
		||||
    discharge_pipe_diameter = models.PositiveIntegerField(null=True, blank=True, verbose_name='قطر لوله آبده (اینچ)')
 | 
			
		||||
    USAGE_TYPE_CHOICES = [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -86,7 +86,11 @@
 | 
			
		|||
                  <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-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>
 | 
			
		||||
                  {% 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-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>
 | 
			
		||||
| 
						 | 
				
			
			@ -279,13 +283,20 @@
 | 
			
		|||
                      <div class="invalid-feedback">{{ form.meter_type.errors.0 }}</div>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="col-md-3">
 | 
			
		||||
                  <div class="col-md-3" id="meter_size_wrapper">
 | 
			
		||||
                    {{ form.meter_size.label_tag }}
 | 
			
		||||
                    {{ form.meter_size }}
 | 
			
		||||
                    {% if form.meter_size.errors %}
 | 
			
		||||
                      <div class="invalid-feedback">{{ form.meter_size.errors.0 }}</div>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                  </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">
 | 
			
		||||
                    {{ form.discharge_pipe_diameter.label_tag }}
 | 
			
		||||
                    {{ form.discharge_pipe_diameter }}
 | 
			
		||||
| 
						 | 
				
			
			@ -329,7 +340,7 @@
 | 
			
		|||
                    {% endif %}
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="col-md-3">
 | 
			
		||||
                    {{ form.water_meter_manufacturer.label_tag }}
 | 
			
		||||
                    {{ form.water_meter_manufacturer.label_tag }}حجمی
 | 
			
		||||
                    <div class="input-group">
 | 
			
		||||
                      {{ form.water_meter_manufacturer }}
 | 
			
		||||
                      {{ form.new_manufacturer }}
 | 
			
		||||
| 
						 | 
				
			
			@ -435,7 +446,7 @@
 | 
			
		|||
                                {% if qi.item.description %}<small class="text-muted">{{ qi.item.description }}</small>{% endif %}
 | 
			
		||||
                              </div>
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td>{{ qi.unit_price|floatformat:0|intcomma:False }} تومان</td>
 | 
			
		||||
                            <td>{{ qi.unit_price|floatformat:0|intcomma:False }} ریال</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>
 | 
			
		||||
                            </td>
 | 
			
		||||
| 
						 | 
				
			
			@ -474,7 +485,7 @@
 | 
			
		|||
                                {% if it.description %}<small class="text-muted">{{ it.description }}</small>{% endif %}
 | 
			
		||||
                              </div>
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td>{{ it.unit_price|floatformat:0|intcomma:False }} تومان</td>
 | 
			
		||||
                            <td>{{ it.unit_price|floatformat:0|intcomma:False }} ریال</td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                              {% 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 %}">
 | 
			
		||||
| 
						 | 
				
			
			@ -505,7 +516,7 @@
 | 
			
		|||
              {% if user_is_installer %}
 | 
			
		||||
                <button type="submit" class="btn btn-success" form="installation-report-form">ثبت گزارش</button>
 | 
			
		||||
              {% endif %}
 | 
			
		||||
              {% if next_step %}
 | 
			
		||||
              {% if next_step and not edit_mode %}
 | 
			
		||||
                <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>
 | 
			
		||||
| 
						 | 
				
			
			@ -759,6 +770,47 @@
 | 
			
		|||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 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>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,13 +57,13 @@ class InvoiceAdmin(SimpleHistoryAdmin):
 | 
			
		|||
    status_display.short_description = "وضعیت"
 | 
			
		||||
 | 
			
		||||
    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 = "مبلغ پرداخت شده"
 | 
			
		||||
 | 
			
		||||
    def remaining_amount_display(self, obj):
 | 
			
		||||
        amount = obj.get_remaining_amount()
 | 
			
		||||
        color = "green" if amount <= 0 else "red"
 | 
			
		||||
        return format_html('<span style="color: {};">{:,.0f} تومان</span>', color, amount)
 | 
			
		||||
        return format_html('<span style="color: {};">{:,.0f} ریال</span>', color, amount)
 | 
			
		||||
    remaining_amount_display.short_description = "مبلغ باقیمانده"
 | 
			
		||||
 | 
			
		||||
@admin.register(Payment)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -38,7 +38,7 @@ class Item(NameSlugModel):
 | 
			
		|||
        ordering = ['name']
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"{self.name} - {self.unit_price} تومان"
 | 
			
		||||
        return f"{self.name} - {self.unit_price} ریال"
 | 
			
		||||
 | 
			
		||||
class Quote(NameSlugModel):
 | 
			
		||||
    """مدل پیشفاکتور"""
 | 
			
		||||
| 
						 | 
				
			
			@ -137,11 +137,11 @@ class Quote(NameSlugModel):
 | 
			
		|||
        return '<span class="badge bg-{}">{}</span>'.format(color, self.get_status_display())
 | 
			
		||||
 | 
			
		||||
    def get_paid_amount(self):
 | 
			
		||||
        """مبلغ پرداخت شده برای این پیشفاکتور بر اساس پرداختهای فاکتور مرتبط"""
 | 
			
		||||
        """خالص پرداختی (دریافتی از مشتری منهای پرداختی به مشتری) برای این پیشفاکتور بر اساس پرداختهای فاکتور مرتبط"""
 | 
			
		||||
        invoice = Invoice.objects.filter(quote=self).first()
 | 
			
		||||
        if not invoice:
 | 
			
		||||
            return Decimal('0')
 | 
			
		||||
        return sum(p.amount for p in invoice.payments.filter(is_deleted=False).all())
 | 
			
		||||
        return sum((p.amount if p.direction == 'in' else -p.amount) for p in invoice.payments.filter(is_deleted=False).all())
 | 
			
		||||
 | 
			
		||||
    def get_remaining_amount(self):
 | 
			
		||||
        """مبلغ باقیمانده بر اساس پرداختها"""
 | 
			
		||||
| 
						 | 
				
			
			@ -151,6 +151,15 @@ class Quote(NameSlugModel):
 | 
			
		|||
            remaining = Decimal('0')
 | 
			
		||||
        return remaining
 | 
			
		||||
 | 
			
		||||
    def get_vat_amount(self) -> Decimal:
 | 
			
		||||
        """محاسبه مبلغ مالیات به صورت جداگانه بر اساس VAT_RATE."""
 | 
			
		||||
        base_amount = (self.total_amount or Decimal('0')) - (self.discount_amount or Decimal('0'))
 | 
			
		||||
        try:
 | 
			
		||||
            vat_rate = Decimal(str(getattr(settings, 'VAT_RATE', 0)))
 | 
			
		||||
        except Exception:
 | 
			
		||||
            vat_rate = Decimal('0')
 | 
			
		||||
        return base_amount * vat_rate
 | 
			
		||||
 | 
			
		||||
class QuoteItem(BaseModel):
 | 
			
		||||
    """مدل آیتمهای پیشفاکتور"""
 | 
			
		||||
    quote = models.ForeignKey(Quote, on_delete=models.CASCADE, related_name='items', verbose_name="پیشفاکتور")
 | 
			
		||||
| 
						 | 
				
			
			@ -291,6 +300,15 @@ class Invoice(NameSlugModel):
 | 
			
		|||
        remaining = self.final_amount - paid
 | 
			
		||||
        return remaining
 | 
			
		||||
 | 
			
		||||
    def get_vat_amount(self) -> Decimal:
 | 
			
		||||
        """محاسبه مبلغ مالیات به صورت جداگانه بر اساس VAT_RATE."""
 | 
			
		||||
        base_amount = (self.total_amount or Decimal('0')) - (self.discount_amount or Decimal('0'))
 | 
			
		||||
        try:
 | 
			
		||||
            vat_rate = Decimal(str(getattr(settings, 'VAT_RATE', 0)))
 | 
			
		||||
        except Exception:
 | 
			
		||||
            vat_rate = Decimal('0')
 | 
			
		||||
        return base_amount * vat_rate
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def get_status_display_with_color(self):
 | 
			
		||||
        """نمایش وضعیت با رنگ"""
 | 
			
		||||
| 
						 | 
				
			
			@ -365,7 +383,7 @@ class Payment(BaseModel):
 | 
			
		|||
        ordering = ['-payment_date']
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"پرداخت {self.amount} تومان - {self.invoice.name}"
 | 
			
		||||
        return f"پرداخت {self.amount} ریال - {self.invoice.name}"
 | 
			
		||||
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        """بروزرسانی مبالغ فاکتور"""
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -124,8 +124,8 @@
 | 
			
		|||
            <th style="width: 30%">شرح کالا/خدمات</th>
 | 
			
		||||
            <th style="width: 30%">توضیحات</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>
 | 
			
		||||
        </thead>
 | 
			
		||||
        <tbody>
 | 
			
		||||
| 
						 | 
				
			
			@ -144,25 +144,29 @@
 | 
			
		|||
        </tbody>
 | 
			
		||||
        <tfoot>
 | 
			
		||||
          <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>
 | 
			
		||||
          </tr>
 | 
			
		||||
          {% if invoice.discount_amount > 0 %}
 | 
			
		||||
          <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>
 | 
			
		||||
          </tr>
 | 
			
		||||
          {% endif %}
 | 
			
		||||
          <tr class="total-section">
 | 
			
		||||
            <td colspan="5" class="text-end"><strong>مالیات بر ارزش افزوده(ریال):</strong></td>
 | 
			
		||||
            <td><strong>{{ invoice.get_vat_amount|floatformat:0|intcomma:False }}</strong></td>
 | 
			
		||||
          </tr>
 | 
			
		||||
          <tr class="total-section border-top border-2">
 | 
			
		||||
            <td colspan="5" class="text-end"><strong>مبلغ نهایی (شامل مالیات)(تومان):</strong></td>
 | 
			
		||||
            <td colspan="5" class="text-end"><strong>مبلغ نهایی (شامل مالیات)(ریال):</strong></td>
 | 
			
		||||
            <td><strong>{{ invoice.final_amount|floatformat:0|intcomma:False }}</strong></td>
 | 
			
		||||
          </tr>
 | 
			
		||||
          <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>
 | 
			
		||||
          </tr>
 | 
			
		||||
          <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>
 | 
			
		||||
          </tr>
 | 
			
		||||
        </tfoot>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -68,19 +68,19 @@
 | 
			
		|||
            <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 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 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 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 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-end">قیمت واحد (تومان)</th>
 | 
			
		||||
                  <th class="text-end">قیمت کل (تومان)</th>
 | 
			
		||||
                  <th class="text-end">قیمت واحد (ریال)</th>
 | 
			
		||||
                  <th class="text-end">قیمت کل (ریال)</th>
 | 
			
		||||
                </tr>
 | 
			
		||||
              </thead>
 | 
			
		||||
              <tbody>
 | 
			
		||||
| 
						 | 
				
			
			@ -153,23 +153,27 @@
 | 
			
		|||
              <tfoot>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <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>
 | 
			
		||||
                  <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>
 | 
			
		||||
                  <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>
 | 
			
		||||
                  <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>
 | 
			
		||||
                  <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>
 | 
			
		||||
              </tfoot>
 | 
			
		||||
            </table>
 | 
			
		||||
| 
						 | 
				
			
			@ -223,8 +227,8 @@
 | 
			
		|||
            </select>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="mb-3">
 | 
			
		||||
            <label class="form-label">مبلغ (تومان)</label>
 | 
			
		||||
            <input type="number" class="form-control" name="amount" id="id_charge_amount" min="1" required>
 | 
			
		||||
            <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>
 | 
			
		||||
          </div>
 | 
			
		||||
        </form>
 | 
			
		||||
      </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -246,8 +250,17 @@
 | 
			
		|||
    else { el.classList.add('show'); el.style.display = 'block'; }
 | 
			
		||||
  }
 | 
			
		||||
  function submitSpecialCharge(){
 | 
			
		||||
    const fd = new FormData(document.getElementById('specialChargeForm'));
 | 
			
		||||
    const form = document.getElementById('specialChargeForm');
 | 
			
		||||
    const fd = new FormData(form);
 | 
			
		||||
    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 })
 | 
			
		||||
      .then(r=>r.json()).then(resp=>{
 | 
			
		||||
        if (resp.success){
 | 
			
		||||
| 
						 | 
				
			
			@ -285,6 +298,8 @@
 | 
			
		|||
        }
 | 
			
		||||
      }).catch(()=> showToast('خطا در ارتباط با سرور', 'danger'));
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // Number formatting is handled by number-formatter.js
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -60,7 +60,7 @@
 | 
			
		|||
        <div class="bs-stepper-content">
 | 
			
		||||
 | 
			
		||||
      <div class="row g-3">
 | 
			
		||||
        {% if is_broker %}
 | 
			
		||||
        {% if is_broker and invoice.get_remaining_amount != 0 %}
 | 
			
		||||
        <div class="col-12 col-lg-5">
 | 
			
		||||
          <div class="card border h-100">
 | 
			
		||||
            <div class="card-header"><h5 class="mb-0">ثبت تراکنش تسویه</h5></div>
 | 
			
		||||
| 
						 | 
				
			
			@ -75,8 +75,8 @@
 | 
			
		|||
                  </select>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="mb-3">
 | 
			
		||||
                  <label class="form-label">مبلغ (تومان)</label>
 | 
			
		||||
                  <input type="number" min="1" class="form-control" name="amount" id="id_amount" required>
 | 
			
		||||
                  <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>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="mb-3">
 | 
			
		||||
                  <label class="form-label">تاریخ</label>
 | 
			
		||||
| 
						 | 
				
			
			@ -122,19 +122,19 @@
 | 
			
		|||
                <div class="col-6 col-md-4">
 | 
			
		||||
                  <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 class="h5 mt-1">{{ invoice.final_amount|floatformat:0|intcomma:False }} ریال</div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="col-6 col-md-4">
 | 
			
		||||
                  <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 class="h5 mt-1 text-success">{{ invoice.get_paid_amount|floatformat:0|intcomma:False }} ریال</div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="col-6 col-md-4">
 | 
			
		||||
                  <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 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 d-flex align-items-center">
 | 
			
		||||
| 
						 | 
				
			
			@ -166,7 +166,7 @@
 | 
			
		|||
                  {% for p in payments %}
 | 
			
		||||
                  <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>{{ p.amount|floatformat:0|intcomma:False }} ریال</td>
 | 
			
		||||
                    <td>{{ p.jpayment_date }}</td>
 | 
			
		||||
                    <td>{{ p.get_payment_method_display }}</td>
 | 
			
		||||
                    <td>{{ p.reference_number|default:'-' }}</td>
 | 
			
		||||
| 
						 | 
				
			
			@ -193,7 +193,7 @@
 | 
			
		|||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      {% if approver_statuses %}
 | 
			
		||||
      {% if approver_statuses and invoice.get_remaining_amount != 0 and step_instance.status != 'completed' %}
 | 
			
		||||
      <div class="card border mt-2">
 | 
			
		||||
        <div class="card-header d-flex justify-content-between align-items-center">
 | 
			
		||||
          <h6 class="mb-0">وضعیت تاییدها</h6>
 | 
			
		||||
| 
						 | 
				
			
			@ -320,7 +320,7 @@
 | 
			
		|||
        <div class="modal-body">
 | 
			
		||||
          {% if invoice.get_remaining_amount != 0 %}
 | 
			
		||||
            <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>
 | 
			
		||||
          {% else %}
 | 
			
		||||
| 
						 | 
				
			
			@ -405,6 +405,14 @@
 | 
			
		|||
 | 
			
		||||
  function buildForm(){
 | 
			
		||||
    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();
 | 
			
		||||
| 
						 | 
				
			
			@ -465,6 +473,24 @@
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  // 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>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,8 +71,8 @@
 | 
			
		|||
                    </div>
 | 
			
		||||
                    <div class="card-body">
 | 
			
		||||
                      <div class="mb-3">
 | 
			
		||||
                        <label class="form-label">مبلغ (تومان)</label>
 | 
			
		||||
                        <input type="number" min="1" class="form-control" name="amount" id="id_amount" required>
 | 
			
		||||
                        <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>
 | 
			
		||||
                      </div>
 | 
			
		||||
                      <div class="mb-3">
 | 
			
		||||
                        <label class="form-label">تاریخ پرداخت/سررسید چک</label>
 | 
			
		||||
| 
						 | 
				
			
			@ -117,19 +117,19 @@
 | 
			
		|||
                        <div class="col-6">
 | 
			
		||||
                          <div class="border rounded p-3">
 | 
			
		||||
                            <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 class="col-6">
 | 
			
		||||
                          <div class="border rounded p-3">
 | 
			
		||||
                            <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 class="col-6">
 | 
			
		||||
                          <div class="border rounded p-3">
 | 
			
		||||
                            <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 class="col-6 d-flex align-items-center">
 | 
			
		||||
| 
						 | 
				
			
			@ -153,6 +153,7 @@
 | 
			
		|||
                      <table class="table table-striped mb-0">
 | 
			
		||||
                        <thead>
 | 
			
		||||
                          <tr>
 | 
			
		||||
                            <th>نوع</th>
 | 
			
		||||
                            <th>مبلغ</th>
 | 
			
		||||
                            <th>تاریخ پرداخت/سررسید چک</th>
 | 
			
		||||
                            <th>روش</th>
 | 
			
		||||
| 
						 | 
				
			
			@ -163,7 +164,8 @@
 | 
			
		|||
                        <tbody>
 | 
			
		||||
                          {% for p in payments %}
 | 
			
		||||
                          <tr>
 | 
			
		||||
                            <td>{{ p.amount|floatformat:0|intcomma:False }} تومان</td>
 | 
			
		||||
                            <td>{% if p.direction == 'in' %}<span class="badge bg-success">دریافتی{% else %}<span class="badge bg-warning text-dark">پرداختی{% endif %}</span></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.get_payment_method_display }}</td>
 | 
			
		||||
                            <td>{{ p.reference_number|default:'-' }}</td>
 | 
			
		||||
| 
						 | 
				
			
			@ -175,9 +177,7 @@
 | 
			
		|||
                                </a>
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                                {% 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 %}
 | 
			
		||||
                              </div>
 | 
			
		||||
                            </td>
 | 
			
		||||
| 
						 | 
				
			
			@ -301,7 +301,7 @@
 | 
			
		|||
          {% if not totals.is_fully_paid %}
 | 
			
		||||
            <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>
 | 
			
		||||
            آیا مطمئن هستید که میخواهید مرحله را تایید کنید؟
 | 
			
		||||
          {% else %}
 | 
			
		||||
| 
						 | 
				
			
			@ -366,6 +366,12 @@
 | 
			
		|||
    }
 | 
			
		||||
    const form = document.getElementById('formAddPayment');
 | 
			
		||||
    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();
 | 
			
		||||
| 
						 | 
				
			
			@ -383,7 +389,7 @@
 | 
			
		|||
          setTimeout(() => { window.location.href = resp.redirect; }, 700);
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        showToast(resp.message + ':' + resp.error || 'خطا در ثبت فیش', 'danger');
 | 
			
		||||
        showToast((resp.message || resp.error || 'خطا در ثبت فیش'), 'danger');
 | 
			
		||||
      }
 | 
			
		||||
    }).catch(() => showToast('خطا در ارتباط با سرور', 'danger'));
 | 
			
		||||
  });
 | 
			
		||||
| 
						 | 
				
			
			@ -460,6 +466,7 @@
 | 
			
		|||
      } catch (e) { console.error('Error initializing Persian Date Picker:', e); }
 | 
			
		||||
    }
 | 
			
		||||
  })();
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -200,9 +200,9 @@
 | 
			
		|||
              <tr>
 | 
			
		||||
                <td class="text-nowrap">{{ quote_item.item.name }}</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.total_price|floatformat:0|intcomma:False }} تومان</td>
 | 
			
		||||
                <td>{{ quote_item.total_price|floatformat:0|intcomma:False }} ریال</td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              {% endfor %}
 | 
			
		||||
              <tr>
 | 
			
		||||
| 
						 | 
				
			
			@ -213,14 +213,16 @@
 | 
			
		|||
                  {% if quote.discount_amount > 0 %}
 | 
			
		||||
                  <p class="mb-2">تخفیف:</p>
 | 
			
		||||
                  {% endif %}
 | 
			
		||||
                  <p class="mb-2">مالیات بر ارزش افزوده:</p>
 | 
			
		||||
                  <p class="mb-0 fw-bold">مبلغ نهایی (شامل مالیات):</p>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="px-4 py-5">
 | 
			
		||||
                  <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 %}
 | 
			
		||||
                  <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 %}
 | 
			
		||||
                  <p class="fw-bold mb-0">{{ quote.final_amount|floatformat:0|intcomma:False }} تومان</p>
 | 
			
		||||
                  <p class="fw-medium mb-2">{{ quote.get_vat_amount|floatformat:0|intcomma:False }} ریال</p>
 | 
			
		||||
                  <p class="fw-bold mb-0">{{ quote.final_amount|floatformat:0|intcomma:False }} ریال</p>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -185,8 +185,8 @@
 | 
			
		|||
                        <th style="width: 30%">شرح کالا/خدمات</th>
 | 
			
		||||
                        <th style="width: 30%">توضیحات</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>
 | 
			
		||||
                </thead>
 | 
			
		||||
                <tbody>
 | 
			
		||||
| 
						 | 
				
			
			@ -203,17 +203,21 @@
 | 
			
		|||
                </tbody>
 | 
			
		||||
                <tfoot>
 | 
			
		||||
                    <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>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    {% if quote.discount_amount > 0 %}
 | 
			
		||||
                    <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>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                    <tr class="total-section">
 | 
			
		||||
                        <td colspan="5" class="text-end"><strong>مالیات بر ارزش افزوده(ریال):</strong></td>
 | 
			
		||||
                        <td><strong>{{ quote.get_vat_amount|floatformat:0|intcomma:False }}</strong></td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr class="total-section border-top border-2">
 | 
			
		||||
                        <td colspan="5" class="text-end"><strong>مبلغ نهایی (با مالیات)(تومان):</strong></td>
 | 
			
		||||
                        <td colspan="5" class="text-end"><strong>مبلغ نهایی (با مالیات)(ریال):</strong></td>
 | 
			
		||||
                        <td><strong>{{ quote.final_amount|floatformat:0|intcomma:False }}</strong></td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                </tfoot>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,7 +57,7 @@
 | 
			
		|||
                  <div class="alert alert-info">
 | 
			
		||||
                    <h6>پیشفاکتور موجود</h6>
 | 
			
		||||
                    <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>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -97,7 +97,7 @@
 | 
			
		|||
                              {% if item.description %}<small class="text-muted">{{ item.description }}</small>{% endif %}
 | 
			
		||||
                            </div>
 | 
			
		||||
                          </td>
 | 
			
		||||
                          <td>{{ item.unit_price|floatformat:0|intcomma:False }} تومان</td>
 | 
			
		||||
                          <td>{{ item.unit_price|floatformat:0|intcomma:False }} ریال</td>
 | 
			
		||||
                          <td>
 | 
			
		||||
                            <input type="number" class="form-control form-control-sm quote-item-qty" min="1"
 | 
			
		||||
                                   data-item-id="{{ item.id }}"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -898,6 +898,29 @@ def add_special_charge(request, instance_id, step_id):
 | 
			
		|||
        unit_price=amount_dec,
 | 
			
		||||
    )
 | 
			
		||||
    invoice.calculate_totals()
 | 
			
		||||
    # If the next step was completed, reopen it (set to in_progress) due to invoice change
 | 
			
		||||
    try:
 | 
			
		||||
        step = get_object_or_404(instance.process.steps, id=step_id)
 | 
			
		||||
        next_step = instance.process.steps.filter(order__gt=step.order).first()
 | 
			
		||||
        if next_step:
 | 
			
		||||
            si, _ = StepInstance.objects.get_or_create(process_instance=instance, step=next_step)
 | 
			
		||||
            if si.status in ['completed', 'approved']:
 | 
			
		||||
                si.status = 'in_progress'
 | 
			
		||||
                si.completed_at = None
 | 
			
		||||
                si.save(update_fields=['status', 'completed_at'])
 | 
			
		||||
                # Clear prior approvals/rejections as the underlying totals changed
 | 
			
		||||
                try:
 | 
			
		||||
                    for appr in list(si.approvals.all()):
 | 
			
		||||
                        appr.delete()
 | 
			
		||||
                except Exception:
 | 
			
		||||
                    pass
 | 
			
		||||
                try:
 | 
			
		||||
                    for rej in list(si.rejections.all()):
 | 
			
		||||
                        rej.delete()
 | 
			
		||||
                except Exception:
 | 
			
		||||
                    pass
 | 
			
		||||
    except Exception:
 | 
			
		||||
        pass
 | 
			
		||||
    return JsonResponse({'success': True, 'redirect': reverse('invoices:final_invoice_step', args=[instance.id, step_id])})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -921,6 +944,29 @@ def delete_special_charge(request, instance_id, step_id, item_id):
 | 
			
		|||
        return JsonResponse({'success': False, 'message': 'امکان حذف این مورد وجود ندارد'})
 | 
			
		||||
    inv_item.hard_delete()
 | 
			
		||||
    invoice.calculate_totals()
 | 
			
		||||
    # If the next step was completed, reopen it (set to in_progress) due to invoice change
 | 
			
		||||
    try:
 | 
			
		||||
        step = get_object_or_404(instance.process.steps, id=step_id)
 | 
			
		||||
        next_step = instance.process.steps.filter(order__gt=step.order).first()
 | 
			
		||||
        if next_step:
 | 
			
		||||
            si, _ = StepInstance.objects.get_or_create(process_instance=instance, step=next_step)
 | 
			
		||||
            if si.status in ['completed', 'approved']:
 | 
			
		||||
                si.status = 'in_progress'
 | 
			
		||||
                si.completed_at = None
 | 
			
		||||
                si.save(update_fields=['status', 'completed_at'])
 | 
			
		||||
                # Clear prior approvals/rejections as the underlying totals changed
 | 
			
		||||
                try:
 | 
			
		||||
                    for appr in list(si.approvals.all()):
 | 
			
		||||
                        appr.delete()
 | 
			
		||||
                except Exception:
 | 
			
		||||
                    pass
 | 
			
		||||
                try:
 | 
			
		||||
                    for rej in list(si.rejections.all()):
 | 
			
		||||
                        rej.delete()
 | 
			
		||||
                except Exception:
 | 
			
		||||
                    pass
 | 
			
		||||
    except Exception:
 | 
			
		||||
        pass
 | 
			
		||||
    return JsonResponse({'success': True, 'redirect': reverse('invoices:final_invoice_step', args=[instance.id, step_id])})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -939,6 +985,23 @@ def final_settlement_step(request, instance_id, step_id):
 | 
			
		|||
 | 
			
		||||
    # Ensure step instance exists
 | 
			
		||||
    step_instance, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step, defaults={'status': 'in_progress'})
 | 
			
		||||
 | 
			
		||||
    # Auto-complete step when invoice is fully settled (no approvals needed)
 | 
			
		||||
    try:
 | 
			
		||||
        invoice.calculate_totals()
 | 
			
		||||
        if invoice.get_remaining_amount() == 0:
 | 
			
		||||
            if step_instance.status != 'completed':
 | 
			
		||||
                step_instance.status = 'completed'
 | 
			
		||||
                step_instance.completed_at = timezone.now()
 | 
			
		||||
                step_instance.save()
 | 
			
		||||
            # if next_step:
 | 
			
		||||
            #     instance.current_step = next_step
 | 
			
		||||
            #     instance.save(update_fields=['current_step'])
 | 
			
		||||
            #     return redirect('processes:step_detail', instance_id=instance.id, step_id=next_step.id)
 | 
			
		||||
            # return redirect('processes:request_list')
 | 
			
		||||
    except Exception:
 | 
			
		||||
        # If totals calculation fails, continue with normal flow
 | 
			
		||||
        pass
 | 
			
		||||
    
 | 
			
		||||
    # Build approver statuses for template (include reason to display in UI)
 | 
			
		||||
    reqs = list(step.approver_requirements.select_related('role').all())
 | 
			
		||||
| 
						 | 
				
			
			@ -1048,6 +1111,14 @@ def final_settlement_step(request, instance_id, step_id):
 | 
			
		|||
            except Exception:
 | 
			
		||||
                messages.error(request, 'فقط مدیر مجاز به تایید اضطراری است.')
 | 
			
		||||
                return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id)
 | 
			
		||||
            # Allow emergency approval only when invoice has a remaining (non-zero)
 | 
			
		||||
            try:
 | 
			
		||||
                invoice.calculate_totals()
 | 
			
		||||
                if invoice.get_remaining_amount() == 0:
 | 
			
		||||
                    messages.error(request, 'فاکتور تسویه شده است؛ تایید اضطراری لازم نیست.')
 | 
			
		||||
                    return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id)
 | 
			
		||||
            except Exception:
 | 
			
		||||
                pass
 | 
			
		||||
            # Mark step completed regardless of remaining amount/approvals
 | 
			
		||||
            step_instance.status = 'approved'
 | 
			
		||||
            step_instance.save()
 | 
			
		||||
| 
						 | 
				
			
			@ -1094,6 +1165,14 @@ def add_final_payment(request, instance_id, step_id):
 | 
			
		|||
    except Exception:
 | 
			
		||||
        return JsonResponse({'success': False, 'message': 'شما مجوز افزودن تراکنش تسویه را ندارید'}, status=403)
 | 
			
		||||
 | 
			
		||||
    # Prevent adding payments if invoice already settled
 | 
			
		||||
    try:
 | 
			
		||||
        invoice.calculate_totals()
 | 
			
		||||
        if invoice.get_remaining_amount() == 0:
 | 
			
		||||
            return JsonResponse({'success': False, 'message': 'فاکتور تسویه شده است؛ افزودن تراکنش مجاز نیست'})
 | 
			
		||||
    except Exception:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    amount = (request.POST.get('amount') or '').strip()
 | 
			
		||||
    payment_date = (request.POST.get('payment_date') or '').strip()
 | 
			
		||||
    payment_method = (request.POST.get('payment_method') or '').strip()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,9 +56,9 @@
 | 
			
		|||
            <div class="card-body">
 | 
			
		||||
              {% if invoice %}
 | 
			
		||||
              <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 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">{{ 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 {% 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 class="table-responsive">
 | 
			
		||||
                <table class="table table-striped mb-0">
 | 
			
		||||
| 
						 | 
				
			
			@ -237,7 +237,7 @@
 | 
			
		|||
                    {% for p in payments %}
 | 
			
		||||
                    <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>{{ p.amount|floatformat:0|intcomma:False }} ریال</td>
 | 
			
		||||
                      <td>{{ p.payment_date|date:'Y/m/d' }}</td>
 | 
			
		||||
                      <td>{{ p.get_payment_method_display }}</td>
 | 
			
		||||
                      <td>{{ p.reference_number|default:'-' }}</td>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -245,7 +245,12 @@
 | 
			
		|||
                <small class="text-muted">{{ item.progress_percentage }}%</small>
 | 
			
		||||
              </div>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>{{ item.instance.get_status_display_with_color|safe }}</td>
 | 
			
		||||
            <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>
 | 
			
		||||
              {% if item.installation_scheduled_date %}
 | 
			
		||||
                <div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -123,6 +123,17 @@ def request_list(request):
 | 
			
		|||
                reference_date = None
 | 
			
		||||
 | 
			
		||||
        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({
 | 
			
		||||
            'instance': instance,
 | 
			
		||||
            'progress_percentage': round(progress_percentage),
 | 
			
		||||
| 
						 | 
				
			
			@ -130,6 +141,7 @@ def request_list(request):
 | 
			
		|||
            'total_steps': total_steps,
 | 
			
		||||
            'installation_scheduled_date': installation_scheduled_date,
 | 
			
		||||
            'installation_overdue_days': overdue_days,
 | 
			
		||||
            'emergency_approved': emergency_approved,
 | 
			
		||||
        })
 | 
			
		||||
    
 | 
			
		||||
    # Summary stats for header cards
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										144
									
								
								static/assets/js/number-formatter.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								static/assets/js/number-formatter.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,144 @@
 | 
			
		|||
/**
 | 
			
		||||
 * 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,6 +169,8 @@ layout-navbar-fixed layout-menu-fixed layout-compact
 | 
			
		|||
<!-- Main JS -->
 | 
			
		||||
<script src="{% static 'assets/js/main.js' %}"></script>
 | 
			
		||||
 | 
			
		||||
<!-- Number Formatter JS -->
 | 
			
		||||
<script src="{% static 'assets/js/number-formatter.js' %}"></script>
 | 
			
		||||
 | 
			
		||||
<!-- Page JS -->
 | 
			
		||||
<script src="{% static 'assets/js/dashboards-analytics.js' %}"></script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue