diff --git a/.gitignore b/.gitignore
index 77e02cd..d975f31 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,8 +9,8 @@
*.pyc
__pycache__/
local_settings.py
-# *.sqlite3
-# db.sqlite3
+*.sqlite3
+db.sqlite3
db.sqlite3-journal
media
#static
diff --git a/installations/forms.py b/installations/forms.py
index 86e7428..e877b2b 100644
--- a/installations/forms.py
+++ b/installations/forms.py
@@ -20,7 +20,7 @@ class InstallationReportForm(forms.ModelForm):
model = InstallationReport
fields = [
'visited_date', 'new_water_meter_serial', 'seal_number',
- 'utm_x', 'utm_y', 'meter_type', 'meter_size',
+ 'utm_x', 'utm_y', 'meter_type', 'meter_size', 'meter_model',
'discharge_pipe_diameter', 'usage_type', 'exploitation_license_number',
'motor_power', 'pre_calibration_flow_rate', 'post_calibration_flow_rate',
'water_meter_manufacturer', 'sim_number', 'driving_force',
@@ -62,6 +62,13 @@ class InstallationReportForm(forms.ModelForm):
'meter_size': forms.TextInput(attrs={
'class': 'form-control'
}),
+ 'meter_model': forms.Select(attrs={
+ 'class': 'form-select'
+ }, choices=[
+ ('', 'انتخاب کنید'),
+ ('A', 'A'),
+ ('B', 'B')
+ ]),
'discharge_pipe_diameter': forms.NumberInput(attrs={
'class': 'form-control',
'min': '0',
diff --git a/installations/models.py b/installations/models.py
index 6eed7a5..6cd48bd 100644
--- a/installations/models.py
+++ b/installations/models.py
@@ -47,6 +47,11 @@ class InstallationReport(BaseModel):
('volumetric', 'حجمی'),
]
meter_type = models.CharField(max_length=20, choices=METER_TYPE_CHOICES, null=True, blank=True, verbose_name='نوع کنتور')
+ METER_MODEL_CHOICES = [
+ ('A', 'A'),
+ ('B', 'B'),
+ ]
+ meter_model = models.CharField(max_length=20, choices=METER_MODEL_CHOICES, null=True, blank=True, verbose_name='مدل کنتور')
meter_size = models.CharField(max_length=50, null=True, blank=True, verbose_name='سایز کنتور')
discharge_pipe_diameter = models.PositiveIntegerField(null=True, blank=True, verbose_name='قطر لوله آبده (اینچ)')
USAGE_TYPE_CHOICES = [
diff --git a/installations/templates/installations/installation_report_step.html b/installations/templates/installations/installation_report_step.html
index d6a7cfc..2d14128 100644
--- a/installations/templates/installations/installation_report_step.html
+++ b/installations/templates/installations/installation_report_step.html
@@ -86,7 +86,11 @@
سریال جدید: {{ report.new_water_meter_serial|default:'-' }}
شماره پلمپ: {{ report.seal_number|default:'-' }}
نوع کنتور: {{ report.get_meter_type_display|default:'-' }}
+ {% if report.meter_type == 'smart' %}
+ مدل کنتور: {{ report.get_meter_model_display|default:'-' }}
+ {% else %}
سایز کنتور: {{ report.meter_size|default:'-' }}
+ {% endif %}
قطر لوله آبده (اینچ): {{ report.discharge_pipe_diameter|default:'-' }}
سازنده کنتور: {{ report.water_meter_manufacturer|default:'-' }}
شماره سیمکارت: {{ report.sim_number|default:'-' }}
@@ -279,13 +283,20 @@
{{ form.meter_type.errors.0 }}
{% endif %}
-
+
{{ form.meter_size.label_tag }}
{{ form.meter_size }}
{% if form.meter_size.errors %}
{{ form.meter_size.errors.0 }}
{% endif %}
+
+ {{ form.meter_model.label_tag }}
+ {{ form.meter_model }}
+ {% if form.meter_model.errors %}
+
{{ form.meter_size.errors.0 }}
+ {% endif %}
+
{{ form.discharge_pipe_diameter.label_tag }}
{{ form.discharge_pipe_diameter }}
@@ -329,7 +340,7 @@
{% endif %}
- {{ form.water_meter_manufacturer.label_tag }}
+ {{ form.water_meter_manufacturer.label_tag }}حجمی
{{ form.water_meter_manufacturer }}
{{ form.new_manufacturer }}
@@ -435,7 +446,7 @@
{% if qi.item.description %}{{ qi.item.description }}{% endif %}
-
{{ qi.unit_price|floatformat:0|intcomma:False }} تومان |
+
{{ qi.unit_price|floatformat:0|intcomma:False }} ریال |
{% if removed_qty|get_item:qi.item.id %}{{ removed_qty|get_item:qi.item.id }}{% else %}{{ qi.quantity }}{% endif %}
|
@@ -474,7 +485,7 @@
{% if it.description %}
{{ it.description }}{% endif %}
-
{{ it.unit_price|floatformat:0|intcomma:False }} تومان |
+
{{ it.unit_price|floatformat:0|intcomma:False }} ریال |
{% with add_entry=added_map|get_item:it.id %}
@@ -505,7 +516,7 @@
{% if user_is_installer %}
{% endif %}
- {% if next_step %}
+ {% if next_step and not edit_mode %}
بعدی
@@ -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);
+ }
+ })();
{% endblock %}
diff --git a/invoices/admin.py b/invoices/admin.py
index 72df296..a53e692 100644
--- a/invoices/admin.py
+++ b/invoices/admin.py
@@ -57,13 +57,13 @@ class InvoiceAdmin(SimpleHistoryAdmin):
status_display.short_description = "وضعیت"
def paid_amount_display(self, obj):
- return f"{obj.get_paid_amount():,.0f} تومان"
+ return f"{obj.get_paid_amount():,.0f} ریال"
paid_amount_display.short_description = "مبلغ پرداخت شده"
def remaining_amount_display(self, obj):
amount = obj.get_remaining_amount()
color = "green" if amount <= 0 else "red"
- return format_html('{:,.0f} تومان', color, amount)
+ return format_html('{:,.0f} ریال', color, amount)
remaining_amount_display.short_description = "مبلغ باقیمانده"
@admin.register(Payment)
diff --git a/invoices/models.py b/invoices/models.py
index 4a48c8c..b93e4b4 100644
--- a/invoices/models.py
+++ b/invoices/models.py
@@ -38,7 +38,7 @@ class Item(NameSlugModel):
ordering = ['name']
def __str__(self):
- return f"{self.name} - {self.unit_price} تومان"
+ return f"{self.name} - {self.unit_price} ریال"
class Quote(NameSlugModel):
"""مدل پیشفاکتور"""
@@ -137,11 +137,11 @@ class Quote(NameSlugModel):
return '{}'.format(color, self.get_status_display())
def get_paid_amount(self):
- """مبلغ پرداخت شده برای این پیشفاکتور بر اساس پرداختهای فاکتور مرتبط"""
+ """خالص پرداختی (دریافتی از مشتری منهای پرداختی به مشتری) برای این پیشفاکتور بر اساس پرداختهای فاکتور مرتبط"""
invoice = Invoice.objects.filter(quote=self).first()
if not invoice:
return Decimal('0')
- return sum(p.amount for p in invoice.payments.filter(is_deleted=False).all())
+ return sum((p.amount if p.direction == 'in' else -p.amount) for p in invoice.payments.filter(is_deleted=False).all())
def get_remaining_amount(self):
"""مبلغ باقیمانده بر اساس پرداختها"""
@@ -151,6 +151,15 @@ class Quote(NameSlugModel):
remaining = Decimal('0')
return remaining
+ def get_vat_amount(self) -> Decimal:
+ """محاسبه مبلغ مالیات به صورت جداگانه بر اساس VAT_RATE."""
+ base_amount = (self.total_amount or Decimal('0')) - (self.discount_amount or Decimal('0'))
+ try:
+ vat_rate = Decimal(str(getattr(settings, 'VAT_RATE', 0)))
+ except Exception:
+ vat_rate = Decimal('0')
+ return base_amount * vat_rate
+
class QuoteItem(BaseModel):
"""مدل آیتمهای پیشفاکتور"""
quote = models.ForeignKey(Quote, on_delete=models.CASCADE, related_name='items', verbose_name="پیشفاکتور")
@@ -291,6 +300,15 @@ class Invoice(NameSlugModel):
remaining = self.final_amount - paid
return remaining
+ def get_vat_amount(self) -> Decimal:
+ """محاسبه مبلغ مالیات به صورت جداگانه بر اساس VAT_RATE."""
+ base_amount = (self.total_amount or Decimal('0')) - (self.discount_amount or Decimal('0'))
+ try:
+ vat_rate = Decimal(str(getattr(settings, 'VAT_RATE', 0)))
+ except Exception:
+ vat_rate = Decimal('0')
+ return base_amount * vat_rate
+
def get_status_display_with_color(self):
"""نمایش وضعیت با رنگ"""
@@ -365,7 +383,7 @@ class Payment(BaseModel):
ordering = ['-payment_date']
def __str__(self):
- return f"پرداخت {self.amount} تومان - {self.invoice.name}"
+ return f"پرداخت {self.amount} ریال - {self.invoice.name}"
def save(self, *args, **kwargs):
"""بروزرسانی مبالغ فاکتور"""
diff --git a/invoices/templates/invoices/final_invoice_print.html b/invoices/templates/invoices/final_invoice_print.html
index d9c8333..36e23f0 100644
--- a/invoices/templates/invoices/final_invoice_print.html
+++ b/invoices/templates/invoices/final_invoice_print.html
@@ -124,8 +124,8 @@
| شرح کالا/خدمات |
توضیحات |
تعداد |
-
قیمت واحد(تومان) |
-
قیمت کل(تومان) |
+
قیمت واحد(ریال) |
+
قیمت کل(ریال) |
@@ -144,25 +144,29 @@
- | جمع کل(تومان): |
+ جمع کل(ریال): |
{{ invoice.total_amount|floatformat:0|intcomma:False }} |
{% if invoice.discount_amount > 0 %}
- | تخفیف(تومان): |
+ تخفیف(ریال): |
{{ invoice.discount_amount|floatformat:0|intcomma:False }} |
{% endif %}
+
+ | مالیات بر ارزش افزوده(ریال): |
+ {{ invoice.get_vat_amount|floatformat:0|intcomma:False }} |
+
- | مبلغ نهایی (شامل مالیات)(تومان): |
+ مبلغ نهایی (شامل مالیات)(ریال): |
{{ invoice.final_amount|floatformat:0|intcomma:False }} |
- | پرداختیها(تومان): |
+ پرداختیها(ریال): |
{{ invoice.get_paid_amount|floatformat:0|intcomma:False }} |
- | مانده(تومان): |
+ مانده(ریال): |
{{ invoice.get_remaining_amount|floatformat:0|intcomma:False }} |
diff --git a/invoices/templates/invoices/final_invoice_step.html b/invoices/templates/invoices/final_invoice_step.html
index 1d99072..c2e3b64 100644
--- a/invoices/templates/invoices/final_invoice_step.html
+++ b/invoices/templates/invoices/final_invoice_step.html
@@ -68,19 +68,19 @@
مبلغ نهایی (با مالیات)
-
{{ invoice.final_amount|floatformat:0|intcomma:False }} تومان
+
{{ invoice.final_amount|floatformat:0|intcomma:False }} ریال
پرداختیها
-
{{ invoice.get_paid_amount|floatformat:0|intcomma:False }} تومان
+
{{ invoice.get_paid_amount|floatformat:0|intcomma:False }} ریال
مانده
-
{{ invoice.get_remaining_amount|floatformat:0|intcomma:False }} تومان
+
{{ invoice.get_remaining_amount|floatformat:0|intcomma:False }} ریال
@@ -100,8 +100,8 @@
افزوده |
حذف |
تعداد نهایی |
- قیمت واحد (تومان) |
- قیمت کل (تومان) |
+ قیمت واحد (ریال) |
+ قیمت کل (ریال) |
@@ -153,23 +153,27 @@
| مبلغ کل |
- {{ invoice.total_amount|floatformat:0|intcomma:False }} تومان |
+ {{ invoice.total_amount|floatformat:0|intcomma:False }} ریال |
| تخفیف |
- {{ invoice.discount_amount|floatformat:0|intcomma:False }} تومان |
+ {{ invoice.discount_amount|floatformat:0|intcomma:False }} ریال |
+
+
+ | مالیات بر ارزش افزوده |
+ {{ invoice.get_vat_amount|floatformat:0|intcomma:False }} ریال |
| مبلغ نهایی (با مالیات) |
- {{ invoice.final_amount|floatformat:0|intcomma:False }} تومان |
+ {{ invoice.final_amount|floatformat:0|intcomma:False }} ریال |
| پرداختیها |
- {{ invoice.get_paid_amount|floatformat:0|intcomma:False }} تومان |
+ {{ invoice.get_paid_amount|floatformat:0|intcomma:False }} ریال |
| مانده |
- {{ invoice.get_remaining_amount|floatformat:0|intcomma:False }} تومان |
+ {{ invoice.get_remaining_amount|floatformat:0|intcomma:False }} ریال |
@@ -223,8 +227,8 @@
-
-
+
+
@@ -246,8 +250,17 @@
else { el.classList.add('show'); el.style.display = 'block'; }
}
function submitSpecialCharge(){
- const fd = new FormData(document.getElementById('specialChargeForm'));
+ const form = document.getElementById('specialChargeForm');
+ const fd = new FormData(form);
fd.append('csrfmiddlewaretoken', document.querySelector('input[name=csrfmiddlewaretoken]').value);
+ // Ensure raw numeric amount is sent
+ (function ensureRawAmount(){
+ const amountInput = document.getElementById('id_charge_amount');
+ if (amountInput){
+ const raw = (amountInput.getAttribute('data-raw-value') || amountInput.value.replace(/\D/g, ''));
+ if (raw) fd.set('amount', raw);
+ }
+ })();
fetch('{% url "invoices:add_special_charge" instance.id step.id %}', { method: 'POST', body: fd })
.then(r=>r.json()).then(resp=>{
if (resp.success){
@@ -285,6 +298,8 @@
}
}).catch(()=> showToast('خطا در ارتباط با سرور', 'danger'));
});
+
+ // Number formatting is handled by number-formatter.js
{% endblock %}
diff --git a/invoices/templates/invoices/final_settlement_step.html b/invoices/templates/invoices/final_settlement_step.html
index a4767d4..0e7916b 100644
--- a/invoices/templates/invoices/final_settlement_step.html
+++ b/invoices/templates/invoices/final_settlement_step.html
@@ -60,7 +60,7 @@
- {% if is_broker %}
+ {% if is_broker and invoice.get_remaining_amount != 0 %}
@@ -75,8 +75,8 @@
-
-
+
+
@@ -122,19 +122,19 @@
مبلغ نهایی (با مالیات)
-
{{ invoice.final_amount|floatformat:0|intcomma:False }} تومان
+
{{ invoice.final_amount|floatformat:0|intcomma:False }} ریال
پرداختیها
-
{{ invoice.get_paid_amount|floatformat:0|intcomma:False }} تومان
+
{{ invoice.get_paid_amount|floatformat:0|intcomma:False }} ریال
مانده
-
{{ invoice.get_remaining_amount|floatformat:0|intcomma:False }} تومان
+
{{ invoice.get_remaining_amount|floatformat:0|intcomma:False }} ریال
@@ -166,7 +166,7 @@
{% for p in payments %}
| {% if p.direction == 'in' %}دریافتی{% else %}پرداختی{% endif %} |
- {{ p.amount|floatformat:0|intcomma:False }} تومان |
+ {{ p.amount|floatformat:0|intcomma:False }} ریال |
{{ p.jpayment_date }} |
{{ p.get_payment_method_display }} |
{{ p.reference_number|default:'-' }} |
@@ -193,7 +193,7 @@
- {% if approver_statuses %}
+ {% if approver_statuses and invoice.get_remaining_amount != 0 and step_instance.status != 'completed' %}