From 0d48e7281a7fd6b3859e7c51ee40e15fe1e3777b Mon Sep 17 00:00:00 2001 From: aminhashemi92 Date: Tue, 7 Oct 2025 07:07:55 +0330 Subject: [PATCH 1/8] fix price seperation --- templates/_base.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/_base.html b/templates/_base.html index 94ebdd1..0a41233 100644 --- a/templates/_base.html +++ b/templates/_base.html @@ -169,6 +169,8 @@ layout-navbar-fixed layout-menu-fixed layout-compact + + From b406f1d7c497e32fa190df33a073215fdec20918 Mon Sep 17 00:00:00 2001 From: aminhashemi92 Date: Tue, 7 Oct 2025 07:08:03 +0330 Subject: [PATCH 2/8] fix price seperation --- .../invoices/final_invoice_step.html | 15 ++++++++-- .../invoices/final_settlement_step.html | 28 ++++++++++++++++++- .../invoices/quote_payment_step.html | 11 ++++++-- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/invoices/templates/invoices/final_invoice_step.html b/invoices/templates/invoices/final_invoice_step.html index 1d99072..17e6730 100644 --- a/invoices/templates/invoices/final_invoice_step.html +++ b/invoices/templates/invoices/final_invoice_step.html @@ -224,7 +224,7 @@
- +
@@ -246,8 +246,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 +294,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..e03a7e2 100644 --- a/invoices/templates/invoices/final_settlement_step.html +++ b/invoices/templates/invoices/final_settlement_step.html @@ -76,7 +76,7 @@
- +
@@ -405,6 +405,14 @@ function buildForm(){ const fd = new FormData(document.getElementById('formFinalPayment')); + // Ensure raw numeric amount is sent + (function ensureRawAmount(){ + const amountInput = document.getElementById('id_amount'); + if (amountInput){ + const raw = (amountInput.getAttribute('data-raw-value') || amountInput.value.replace(/\D/g, '')); + if (raw) fd.set('amount', raw); + } + })(); // تبدیل تاریخ شمسی به میلادی برای ارسال const persianDateValue = $('#id_payment_date').val(); @@ -465,6 +473,24 @@ } // Legacy approve button removed; using modal forms below + + // Handle AJAX form submission with number formatting + $(document).ready(function() { + // Override buildForm function for AJAX submission + const originalBuildForm = window.buildForm; + window.buildForm = function() { + // Set raw values before creating FormData + if (window.setRawValuesForSubmission) { + window.setRawValuesForSubmission(); + } + const result = originalBuildForm ? originalBuildForm() : new FormData(document.querySelector('form')); + // Restore formatted values for display + if (window.restoreFormattedValues) { + window.restoreFormattedValues(); + } + return result; + }; + }); {% endblock %} diff --git a/invoices/templates/invoices/quote_payment_step.html b/invoices/templates/invoices/quote_payment_step.html index e9bc07f..4a071f8 100644 --- a/invoices/templates/invoices/quote_payment_step.html +++ b/invoices/templates/invoices/quote_payment_step.html @@ -72,7 +72,7 @@
- +
@@ -366,6 +366,12 @@ } const form = document.getElementById('formAddPayment'); const fd = buildFormData(form); + // Ensure raw numeric amount is sent + (function ensureRawAmount(){ + const amountInput = document.getElementById('id_amount'); + const raw = (amountInput.getAttribute('data-raw-value') || amountInput.value.replace(/\D/g, '')); + if (raw) fd.set('amount', raw); + })(); // تبدیل تاریخ شمسی به میلادی برای ارسال const persianDateValue = $('#id_payment_date').val(); @@ -383,7 +389,7 @@ setTimeout(() => { window.location.href = resp.redirect; }, 700); } } else { - showToast(resp.message + ':' + resp.error || 'خطا در ثبت فیش', 'danger'); + showToast((resp.message || resp.error || 'خطا در ثبت فیش'), 'danger'); } }).catch(() => showToast('خطا در ارتباط با سرور', 'danger')); }); @@ -460,6 +466,7 @@ } catch (e) { console.error('Error initializing Persian Date Picker:', e); } } })(); + {% endblock %} From 65cc48769dc3f77544da05f90ed1ef5aa759bdcc Mon Sep 17 00:00:00 2001 From: aminhashemi92 Date: Tue, 7 Oct 2025 07:08:23 +0330 Subject: [PATCH 3/8] fix price seperation --- static/assets/js/number-formatter.js | 144 +++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 static/assets/js/number-formatter.js diff --git a/static/assets/js/number-formatter.js b/static/assets/js/number-formatter.js new file mode 100644 index 0000000..47b2e88 --- /dev/null +++ b/static/assets/js/number-formatter.js @@ -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); }; +}); From 169a9bd6247ec9c9878890656f7cc17181a2df0e Mon Sep 17 00:00:00 2001 From: aminhashemi92 Date: Tue, 7 Oct 2025 07:45:09 +0330 Subject: [PATCH 4/8] fix force approve and vat show --- .gitignore | 4 +- invoices/models.py | 18 +++++ .../invoices/final_invoice_print.html | 4 + .../invoices/final_invoice_step.html | 4 + .../invoices/final_settlement_step.html | 4 +- .../invoices/quote_preview_step.html | 2 + invoices/templates/invoices/quote_print.html | 4 + invoices/views.py | 79 +++++++++++++++++++ .../templates/processes/request_list.html | 7 +- processes/views.py | 12 +++ 10 files changed, 133 insertions(+), 5 deletions(-) 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/invoices/models.py b/invoices/models.py index 4a48c8c..89dcd50 100644 --- a/invoices/models.py +++ b/invoices/models.py @@ -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): """نمایش وضعیت با رنگ""" diff --git a/invoices/templates/invoices/final_invoice_print.html b/invoices/templates/invoices/final_invoice_print.html index d9c8333..1b28c4e 100644 --- a/invoices/templates/invoices/final_invoice_print.html +++ b/invoices/templates/invoices/final_invoice_print.html @@ -153,6 +153,10 @@ {{ invoice.discount_amount|floatformat:0|intcomma:False }} {% endif %} + + مالیات بر ارزش افزوده(تومان): + {{ invoice.get_vat_amount|floatformat:0|intcomma:False }} + مبلغ نهایی (شامل مالیات)(تومان): {{ invoice.final_amount|floatformat:0|intcomma:False }} diff --git a/invoices/templates/invoices/final_invoice_step.html b/invoices/templates/invoices/final_invoice_step.html index 17e6730..3200a48 100644 --- a/invoices/templates/invoices/final_invoice_step.html +++ b/invoices/templates/invoices/final_invoice_step.html @@ -159,6 +159,10 @@ تخفیف {{ invoice.discount_amount|floatformat:0|intcomma:False }} تومان + + مالیات بر ارزش افزوده + {{ invoice.get_vat_amount|floatformat:0|intcomma:False }} تومان + مبلغ نهایی (با مالیات) {{ invoice.final_amount|floatformat:0|intcomma:False }} تومان diff --git a/invoices/templates/invoices/final_settlement_step.html b/invoices/templates/invoices/final_settlement_step.html index e03a7e2..e32fa41 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 %}
ثبت تراکنش تسویه
@@ -193,7 +193,7 @@
- {% if approver_statuses %} + {% if approver_statuses and invoice.get_remaining_amount != 0 and step_instance.status != 'completed' %}
وضعیت تاییدها
diff --git a/invoices/templates/invoices/quote_preview_step.html b/invoices/templates/invoices/quote_preview_step.html index eeead73..08e3bd8 100644 --- a/invoices/templates/invoices/quote_preview_step.html +++ b/invoices/templates/invoices/quote_preview_step.html @@ -213,6 +213,7 @@ {% if quote.discount_amount > 0 %}

تخفیف:

{% endif %} +

مالیات بر ارزش افزوده:

مبلغ نهایی (شامل مالیات):

@@ -220,6 +221,7 @@ {% if quote.discount_amount > 0 %}

{{ quote.discount_amount|floatformat:0|intcomma:False }} تومان

{% endif %} +

{{ quote.get_vat_amount|floatformat:0|intcomma:False }} تومان

{{ quote.final_amount|floatformat:0|intcomma:False }} تومان

diff --git a/invoices/templates/invoices/quote_print.html b/invoices/templates/invoices/quote_print.html index fc445a6..fb67046 100644 --- a/invoices/templates/invoices/quote_print.html +++ b/invoices/templates/invoices/quote_print.html @@ -212,6 +212,10 @@ {{ quote.discount_amount|floatformat:0|intcomma:False }} {% endif %} + + مالیات بر ارزش افزوده(تومان): + {{ quote.get_vat_amount|floatformat:0|intcomma:False }} + مبلغ نهایی (با مالیات)(تومان): {{ quote.final_amount|floatformat:0|intcomma:False }} diff --git a/invoices/views.py b/invoices/views.py index 778d837..04ea85c 100644 --- a/invoices/views.py +++ b/invoices/views.py @@ -898,6 +898,29 @@ def add_special_charge(request, instance_id, step_id): unit_price=amount_dec, ) invoice.calculate_totals() + # If the next step was completed, reopen it (set to in_progress) due to invoice change + try: + step = get_object_or_404(instance.process.steps, id=step_id) + next_step = instance.process.steps.filter(order__gt=step.order).first() + if next_step: + si, _ = StepInstance.objects.get_or_create(process_instance=instance, step=next_step) + if si.status in ['completed', 'approved']: + si.status = 'in_progress' + si.completed_at = None + si.save(update_fields=['status', 'completed_at']) + # Clear prior approvals/rejections as the underlying totals changed + try: + for appr in list(si.approvals.all()): + appr.delete() + except Exception: + pass + try: + for rej in list(si.rejections.all()): + rej.delete() + except Exception: + pass + except Exception: + pass return JsonResponse({'success': True, 'redirect': reverse('invoices:final_invoice_step', args=[instance.id, step_id])}) @@ -921,6 +944,29 @@ def delete_special_charge(request, instance_id, step_id, item_id): return JsonResponse({'success': False, 'message': 'امکان حذف این مورد وجود ندارد'}) inv_item.hard_delete() invoice.calculate_totals() + # If the next step was completed, reopen it (set to in_progress) due to invoice change + try: + step = get_object_or_404(instance.process.steps, id=step_id) + next_step = instance.process.steps.filter(order__gt=step.order).first() + if next_step: + si, _ = StepInstance.objects.get_or_create(process_instance=instance, step=next_step) + if si.status in ['completed', 'approved']: + si.status = 'in_progress' + si.completed_at = None + si.save(update_fields=['status', 'completed_at']) + # Clear prior approvals/rejections as the underlying totals changed + try: + for appr in list(si.approvals.all()): + appr.delete() + except Exception: + pass + try: + for rej in list(si.rejections.all()): + rej.delete() + except Exception: + pass + except Exception: + pass return JsonResponse({'success': True, 'redirect': reverse('invoices:final_invoice_step', args=[instance.id, step_id])}) @@ -939,6 +985,23 @@ def final_settlement_step(request, instance_id, step_id): # Ensure step instance exists step_instance, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step, defaults={'status': 'in_progress'}) + + # Auto-complete step when invoice is fully settled (no approvals needed) + try: + invoice.calculate_totals() + if invoice.get_remaining_amount() == 0: + if step_instance.status != 'completed': + step_instance.status = 'completed' + step_instance.completed_at = timezone.now() + step_instance.save() + # if next_step: + # instance.current_step = next_step + # instance.save(update_fields=['current_step']) + # return redirect('processes:step_detail', instance_id=instance.id, step_id=next_step.id) + # return redirect('processes:request_list') + except Exception: + # If totals calculation fails, continue with normal flow + pass # Build approver statuses for template (include reason to display in UI) reqs = list(step.approver_requirements.select_related('role').all()) @@ -1048,6 +1111,14 @@ def final_settlement_step(request, instance_id, step_id): except Exception: messages.error(request, 'فقط مدیر مجاز به تایید اضطراری است.') return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id) + # Allow emergency approval only when invoice has a remaining (non-zero) + try: + invoice.calculate_totals() + if invoice.get_remaining_amount() == 0: + messages.error(request, 'فاکتور تسویه شده است؛ تایید اضطراری لازم نیست.') + return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id) + except Exception: + pass # Mark step completed regardless of remaining amount/approvals step_instance.status = 'approved' step_instance.save() @@ -1094,6 +1165,14 @@ def add_final_payment(request, instance_id, step_id): except Exception: return JsonResponse({'success': False, 'message': 'شما مجوز افزودن تراکنش تسویه را ندارید'}, status=403) + # Prevent adding payments if invoice already settled + try: + invoice.calculate_totals() + if invoice.get_remaining_amount() == 0: + return JsonResponse({'success': False, 'message': 'فاکتور تسویه شده است؛ افزودن تراکنش مجاز نیست'}) + except Exception: + pass + amount = (request.POST.get('amount') or '').strip() payment_date = (request.POST.get('payment_date') or '').strip() payment_method = (request.POST.get('payment_method') or '').strip() diff --git a/processes/templates/processes/request_list.html b/processes/templates/processes/request_list.html index 9ec7e92..eee1727 100644 --- a/processes/templates/processes/request_list.html +++ b/processes/templates/processes/request_list.html @@ -245,7 +245,12 @@ {{ item.progress_percentage }}%
- {{ item.instance.get_status_display_with_color|safe }} + + {{ item.instance.get_status_display_with_color|safe }} + {% if item.emergency_approved %} + تایید اضطراری + {% endif %} + {% if item.installation_scheduled_date %}
diff --git a/processes/views.py b/processes/views.py index e959b2e..372c8e7 100644 --- a/processes/views.py +++ b/processes/views.py @@ -123,6 +123,17 @@ def request_list(request): reference_date = None installation_scheduled_date = reference_date if reference_date and reference_date > sched_date else sched_date + + # Emergency approved flag (final settlement step forced approval) + try: + final_settlement_step = instance.process.steps.filter(order=8).first() + emergency_approved = False + if final_settlement_step: + si = instance.step_instances.filter(step=final_settlement_step).first() + emergency_approved = bool(si and si.status == 'approved') + except Exception: + emergency_approved = False + instances_with_progress.append({ 'instance': instance, 'progress_percentage': round(progress_percentage), @@ -130,6 +141,7 @@ def request_list(request): 'total_steps': total_steps, 'installation_scheduled_date': installation_scheduled_date, 'installation_overdue_days': overdue_days, + 'emergency_approved': emergency_approved, }) # Summary stats for header cards From dd64b7b47cda3c397e2aa939782c0727eafaa652 Mon Sep 17 00:00:00 2001 From: aminhashemi92 Date: Tue, 7 Oct 2025 07:59:52 +0330 Subject: [PATCH 5/8] fix quote payments show --- invoices/models.py | 4 ++-- invoices/templates/invoices/quote_payment_step.html | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/invoices/models.py b/invoices/models.py index 89dcd50..93fcfc9 100644 --- a/invoices/models.py +++ b/invoices/models.py @@ -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): """مبلغ باقی‌مانده بر اساس پرداخت‌ها""" diff --git a/invoices/templates/invoices/quote_payment_step.html b/invoices/templates/invoices/quote_payment_step.html index 4a071f8..05096e4 100644 --- a/invoices/templates/invoices/quote_payment_step.html +++ b/invoices/templates/invoices/quote_payment_step.html @@ -153,6 +153,7 @@ + @@ -163,7 +164,8 @@ {% for p in payments %} - + + @@ -175,9 +177,7 @@ {% endif %} {% if is_broker %} - + {% endif %} From 02415f4dee784402b7edfbde80d7cf74e45134ff Mon Sep 17 00:00:00 2001 From: aminhashemi92 Date: Tue, 7 Oct 2025 08:01:58 +0330 Subject: [PATCH 6/8] change to rial --- .../installation_report_step.html | 4 ++-- invoices/admin.py | 4 ++-- invoices/models.py | 4 ++-- .../invoices/final_invoice_print.html | 16 ++++++------- .../invoices/final_invoice_step.html | 24 +++++++++---------- .../invoices/final_settlement_step.html | 12 +++++----- .../invoices/quote_payment_step.html | 12 +++++----- .../invoices/quote_preview_step.html | 12 +++++----- invoices/templates/invoices/quote_print.html | 12 +++++----- invoices/templates/invoices/quote_step.html | 4 ++-- .../templates/processes/instance_summary.html | 8 +++---- 11 files changed, 56 insertions(+), 56 deletions(-) diff --git a/installations/templates/installations/installation_report_step.html b/installations/templates/installations/installation_report_step.html index d6a7cfc..5f6020f 100644 --- a/installations/templates/installations/installation_report_step.html +++ b/installations/templates/installations/installation_report_step.html @@ -435,7 +435,7 @@ {% if qi.item.description %}{{ qi.item.description }}{% endif %} - + @@ -474,7 +474,7 @@ {% if it.description %}{{ it.description }}{% endif %} - + - - + + @@ -144,29 +144,29 @@ - + {% if invoice.discount_amount > 0 %} - + {% endif %} - + - + - + - + diff --git a/invoices/templates/invoices/final_invoice_step.html b/invoices/templates/invoices/final_invoice_step.html index 3200a48..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,27 +153,27 @@ - + - + - + - + - + - +
نوع مبلغ تاریخ پرداخت/سررسید چک روش
{{ p.amount|floatformat:0|intcomma:False }} تومان{% if p.direction == 'in' %}دریافتی{% else %}پرداختی{% endif %}{{ p.amount|floatformat:0|intcomma:False }} تومان {{ p.jpayment_date }} {{ p.get_payment_method_display }} {{ p.reference_number|default:'-' }} {{ 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 %} {{ it.unit_price|floatformat:0|intcomma:False }} تومان{{ it.unit_price|floatformat:0|intcomma:False }} ریال {% with add_entry=added_map|get_item:it.id %} 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 93fcfc9..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): """مدل پیش‌فاکتور""" @@ -383,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 1b28c4e..36e23f0 100644 --- a/invoices/templates/invoices/final_invoice_print.html +++ b/invoices/templates/invoices/final_invoice_print.html @@ -124,8 +124,8 @@ شرح کالا/خدمات توضیحات تعدادقیمت واحد(تومان)قیمت کل(تومان)قیمت واحد(ریال)قیمت کل(ریال)
جمع کل(تومان):جمع کل(ریال): {{ invoice.total_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.get_paid_amount|floatformat:0|intcomma:False }}
مانده(تومان):مانده(ریال): {{ invoice.get_remaining_amount|floatformat:0|intcomma:False }}
افزوده حذف تعداد نهاییقیمت واحد (تومان)قیمت کل (تومان)قیمت واحد (ریال)قیمت کل (ریال)
مبلغ کل{{ 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.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 }} ریال
@@ -227,7 +227,7 @@
- +
diff --git a/invoices/templates/invoices/final_settlement_step.html b/invoices/templates/invoices/final_settlement_step.html index e32fa41..0e7916b 100644 --- a/invoices/templates/invoices/final_settlement_step.html +++ b/invoices/templates/invoices/final_settlement_step.html @@ -75,7 +75,7 @@
- +
@@ -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:'-' }} @@ -320,7 +320,7 @@
- +
@@ -117,19 +117,19 @@
مبلغ نهایی پیش‌فاکتور (با مالیات)
-
{{ totals.final_amount|floatformat:0|intcomma:False }} تومان
+
{{ totals.final_amount|floatformat:0|intcomma:False }} ریال
مبلغ پرداخت‌شده
-
{{ totals.paid_amount|floatformat:0|intcomma:False }} تومان
+
{{ totals.paid_amount|floatformat:0|intcomma:False }} ریال
مانده
-
{{ totals.remaining_amount|floatformat:0|intcomma:False }} تومان
+
{{ totals.remaining_amount|floatformat:0|intcomma:False }} ریال
@@ -165,7 +165,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:'-' }} @@ -301,7 +301,7 @@ {% if not totals.is_fully_paid %} آیا مطمئن هستید که می‌خواهید مرحله را تایید کنید؟ {% else %} diff --git a/invoices/templates/invoices/quote_preview_step.html b/invoices/templates/invoices/quote_preview_step.html index 08e3bd8..0742094 100644 --- a/invoices/templates/invoices/quote_preview_step.html +++ b/invoices/templates/invoices/quote_preview_step.html @@ -200,9 +200,9 @@ {{ quote_item.item.name }} {{ quote_item.item.description|default:"-" }} - {{ quote_item.unit_price|floatformat:0|intcomma:False }} تومان + {{ quote_item.unit_price|floatformat:0|intcomma:False }} ریال {{ quote_item.quantity }} - {{ quote_item.total_price|floatformat:0|intcomma:False }} تومان + {{ quote_item.total_price|floatformat:0|intcomma:False }} ریال {% endfor %} @@ -217,12 +217,12 @@

مبلغ نهایی (شامل مالیات):

-

{{ quote.total_amount|floatformat:0|intcomma:False }} تومان

+

{{ quote.total_amount|floatformat:0|intcomma:False }} ریال

{% if quote.discount_amount > 0 %} -

{{ quote.discount_amount|floatformat:0|intcomma:False }} تومان

+

{{ quote.discount_amount|floatformat:0|intcomma:False }} ریال

{% endif %} -

{{ quote.get_vat_amount|floatformat:0|intcomma:False }} تومان

-

{{ quote.final_amount|floatformat:0|intcomma:False }} تومان

+

{{ quote.get_vat_amount|floatformat:0|intcomma:False }} ریال

+

{{ quote.final_amount|floatformat:0|intcomma:False }} ریال

diff --git a/invoices/templates/invoices/quote_print.html b/invoices/templates/invoices/quote_print.html index fb67046..052f579 100644 --- a/invoices/templates/invoices/quote_print.html +++ b/invoices/templates/invoices/quote_print.html @@ -185,8 +185,8 @@ شرح کالا/خدمات توضیحات تعداد - قیمت واحد(تومان) - قیمت کل(تومان) + قیمت واحد(ریال) + قیمت کل(ریال) @@ -203,21 +203,21 @@ - جمع کل(تومان): + جمع کل(ریال): {{ quote.total_amount|floatformat:0|intcomma:False }} {% if quote.discount_amount > 0 %} - تخفیف(تومان): + تخفیف(ریال): {{ quote.discount_amount|floatformat:0|intcomma:False }} {% endif %} - مالیات بر ارزش افزوده(تومان): + مالیات بر ارزش افزوده(ریال): {{ quote.get_vat_amount|floatformat:0|intcomma:False }} - مبلغ نهایی (با مالیات)(تومان): + مبلغ نهایی (با مالیات)(ریال): {{ quote.final_amount|floatformat:0|intcomma:False }} diff --git a/invoices/templates/invoices/quote_step.html b/invoices/templates/invoices/quote_step.html index 853f250..82ec091 100644 --- a/invoices/templates/invoices/quote_step.html +++ b/invoices/templates/invoices/quote_step.html @@ -57,7 +57,7 @@
پیش‌فاکتور موجود
{{ existing_quote.name }} | - مبلغ کل (با احتساب مالیات): {{ existing_quote.final_amount|floatformat:0|intcomma:False }} تومان | + مبلغ کل (با احتساب مالیات): {{ existing_quote.final_amount|floatformat:0|intcomma:False }} ریال | وضعیت: {{ existing_quote.get_status_display_with_color|safe }}
@@ -97,7 +97,7 @@ {% if item.description %}{{ item.description }}{% endif %}
- {{ item.unit_price|floatformat:0|intcomma:False }} تومان + {{ item.unit_price|floatformat:0|intcomma:False }} ریال {% if invoice %}
-
مبلغ نهایی
{{ invoice.final_amount|floatformat:0|intcomma:False }} تومان
-
پرداختی‌ها
{{ invoice.get_paid_amount|floatformat:0|intcomma:False }} تومان
-
مانده
{{ invoice.get_remaining_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 }} ریال
@@ -237,7 +237,7 @@ {% for p in payments %} - + From 68cf7761d7c7bb3ced8277715f71d8a8ec0a78d1 Mon Sep 17 00:00:00 2001 From: aminhashemi92 Date: Tue, 7 Oct 2025 08:31:40 +0330 Subject: [PATCH 7/8] add meter model for smart meter --- installations/forms.py | 9 ++- installations/models.py | 5 ++ .../installation_report_step.html | 56 ++++++++++++++++++- 3 files changed, 67 insertions(+), 3 deletions(-) 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 5f6020f..745fc45 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 }} @@ -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 %} From 9daec9c8f1fe3e1c939d49a518be9a36025ca4bb Mon Sep 17 00:00:00 2001 From: aminhashemi92 Date: Tue, 7 Oct 2025 08:33:21 +0330 Subject: [PATCH 8/8] next step only in not edit mode --- .../templates/installations/installation_report_step.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installations/templates/installations/installation_report_step.html b/installations/templates/installations/installation_report_step.html index 745fc45..2d14128 100644 --- a/installations/templates/installations/installation_report_step.html +++ b/installations/templates/installations/installation_report_step.html @@ -516,7 +516,7 @@ {% if user_is_installer %} {% endif %} - {% if next_step %} + {% if next_step and not edit_mode %} بعدی
{% if p.direction == 'in' %}دریافتی{% else %}پرداختی{% endif %}{{ p.amount|floatformat:0|intcomma:False }} تومان{{ p.amount|floatformat:0|intcomma:False }} ریال {{ p.payment_date|date:'Y/m/d' }} {{ p.get_payment_method_display }} {{ p.reference_number|default:'-' }}