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
 | 
					*.pyc
 | 
				
			||||||
__pycache__/
 | 
					__pycache__/
 | 
				
			||||||
local_settings.py
 | 
					local_settings.py
 | 
				
			||||||
# *.sqlite3
 | 
					*.sqlite3
 | 
				
			||||||
# db.sqlite3
 | 
					db.sqlite3
 | 
				
			||||||
db.sqlite3-journal
 | 
					db.sqlite3-journal
 | 
				
			||||||
media
 | 
					media
 | 
				
			||||||
#static
 | 
					#static
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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',
 | 
					            'utm_x', 'utm_y', 'meter_type', 'meter_size', 'meter_model',
 | 
				
			||||||
            '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,6 +62,13 @@ 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',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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', 'حجمی'),
 | 
					        ('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,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-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>
 | 
				
			||||||
| 
						 | 
					@ -279,13 +283,20 @@
 | 
				
			||||||
                      <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">
 | 
					                  <div class="col-md-3" id="meter_size_wrapper">
 | 
				
			||||||
                    {{ 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 }}
 | 
				
			||||||
| 
						 | 
					@ -329,7 +340,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 }}
 | 
				
			||||||
| 
						 | 
					@ -435,7 +446,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>
 | 
				
			||||||
| 
						 | 
					@ -474,7 +485,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 %}">
 | 
				
			||||||
| 
						 | 
					@ -505,7 +516,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 %}
 | 
					              {% if next_step and not edit_mode %}
 | 
				
			||||||
                <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>
 | 
				
			||||||
| 
						 | 
					@ -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>
 | 
					</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 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):
 | 
					    def get_remaining_amount(self):
 | 
				
			||||||
        """مبلغ باقیمانده بر اساس پرداختها"""
 | 
					        """مبلغ باقیمانده بر اساس پرداختها"""
 | 
				
			||||||
| 
						 | 
					@ -151,6 +151,15 @@ 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="پیشفاکتور")
 | 
				
			||||||
| 
						 | 
					@ -291,6 +300,15 @@ 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):
 | 
				
			||||||
        """نمایش وضعیت با رنگ"""
 | 
					        """نمایش وضعیت با رنگ"""
 | 
				
			||||||
| 
						 | 
					@ -365,7 +383,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,25 +144,29 @@
 | 
				
			||||||
        </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,23 +153,27 @@
 | 
				
			||||||
              <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>
 | 
				
			||||||
| 
						 | 
					@ -223,8 +227,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="number" class="form-control" name="amount" id="id_charge_amount" min="1" required>
 | 
					            <input type="text" inputmode="numeric" pattern="\d*" class="form-control" name="amount" id="id_charge_amount" dir="ltr" autocomplete="off" required>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </form>
 | 
					        </form>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
| 
						 | 
					@ -246,8 +250,17 @@
 | 
				
			||||||
    else { el.classList.add('show'); el.style.display = 'block'; }
 | 
					    else { el.classList.add('show'); el.style.display = 'block'; }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  function submitSpecialCharge(){
 | 
					  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);
 | 
					    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){
 | 
				
			||||||
| 
						 | 
					@ -285,6 +298,8 @@
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }).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 %}
 | 
					        {% if is_broker and invoice.get_remaining_amount != 0 %}
 | 
				
			||||||
        <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="number" min="1" class="form-control" name="amount" id="id_amount" required>
 | 
					                  <input type="text" inputmode="numeric" pattern="\d*" class="form-control" name="amount" id="id_amount" dir="ltr" autocomplete="off" 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 %}
 | 
					      {% if approver_statuses and invoice.get_remaining_amount != 0 and step_instance.status != 'completed' %}
 | 
				
			||||||
      <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,6 +405,14 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  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();
 | 
				
			||||||
| 
						 | 
					@ -465,6 +473,24 @@
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // 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="number" min="1" class="form-control" name="amount" id="id_amount" required>
 | 
					                        <input type="text" inputmode="numeric" pattern="\d*" class="form-control" name="amount" id="id_amount" dir="ltr" autocomplete="off" 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,6 +153,7 @@
 | 
				
			||||||
                      <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>
 | 
				
			||||||
| 
						 | 
					@ -163,7 +164,8 @@
 | 
				
			||||||
                        <tbody>
 | 
					                        <tbody>
 | 
				
			||||||
                          {% for p in payments %}
 | 
					                          {% for p in payments %}
 | 
				
			||||||
                          <tr>
 | 
					                          <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.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>
 | 
				
			||||||
| 
						 | 
					@ -175,9 +177,7 @@
 | 
				
			||||||
                                </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="حذف">
 | 
					                                <button type="button" class="btn btn-sm btn-outline-danger" onclick="openDeleteModal('{{ p.id }}')" title="حذف" aria-label="حذف"><i class="bx bx-trash"></i></button>
 | 
				
			||||||
                                  <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,6 +366,12 @@
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    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();
 | 
				
			||||||
| 
						 | 
					@ -383,7 +389,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'));
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					@ -460,6 +466,7 @@
 | 
				
			||||||
      } 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,14 +213,16 @@
 | 
				
			||||||
                  {% 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-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>
 | 
					                </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,17 +203,21 @@
 | 
				
			||||||
                </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,6 +898,29 @@ 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])})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -921,6 +944,29 @@ 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])})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -939,6 +985,23 @@ 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())
 | 
				
			||||||
| 
						 | 
					@ -1048,6 +1111,14 @@ 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()
 | 
				
			||||||
| 
						 | 
					@ -1094,6 +1165,14 @@ 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,7 +245,12 @@
 | 
				
			||||||
                <small class="text-muted">{{ item.progress_percentage }}%</small>
 | 
					                <small class="text-muted">{{ item.progress_percentage }}%</small>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </td>
 | 
					            </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>
 | 
					            <td>
 | 
				
			||||||
              {% if item.installation_scheduled_date %}
 | 
					              {% if item.installation_scheduled_date %}
 | 
				
			||||||
                <div>
 | 
					                <div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -123,6 +123,17 @@ 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),
 | 
				
			||||||
| 
						 | 
					@ -130,6 +141,7 @@ 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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										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 -->
 | 
					<!-- 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