diff --git a/_base/settings.py b/_base/settings.py index 3710f84..7466d95 100644 --- a/_base/settings.py +++ b/_base/settings.py @@ -179,15 +179,15 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' JAZZMIN_SETTINGS = { # title of the window (Will default to current_admin_site.site_title if absent or None) - "site_title": "کنتور پلاس", + "site_title": "سامانه شفافیت", # Title on the login screen (19 chars max) (defaults to current_admin_site.site_header if absent or None) - "site_header": "کنتور پلاس", + "site_header": "سامانه شفافیت", # Title on the brand (19 chars max) (defaults to current_admin_site.site_header if absent or None) - "site_brand": "کنتور پلاس", + "site_brand": "سامانه شفافیت", # Welcome text on the login screen - "welcome_sign": "به کنتور پلاس خوش آمدید", + "welcome_sign": "به سامانه شفافیت خوش آمدید", # Copyright on the footer - "copyright": "کنتور پلاس", + "copyright": "سامانه شفافیت", # Logo to use for your site, must be present in static files, used for brand on top left # "site_logo": "../static/dist/img/iconlogo.png", # Relative paths to custom CSS/JS scripts (must be present in static files) diff --git a/_helpers/utils.py b/_helpers/utils.py index 92f24b4..a7adccb 100644 --- a/_helpers/utils.py +++ b/_helpers/utils.py @@ -192,122 +192,4 @@ def normalize_size(size: int) -> str: return f"{int(size_mb)} MB" if size_mb.is_integer() else f"{size_mb:.1f} MB" else: size_gb = size / (1024 * 1024 * 1024) - return f"{int(size_gb)} GB" if size_gb.is_integer() else f"{size_gb:.1f} GB" - - -def number_to_persian_words(number): - """ - تبدیل عدد به حروف فارسی - مثال: 12345 -> دوازده هزار و سیصد و چهل و پنج - """ - try: - # تبدیل به عدد صحیح (در صورت نیاز) - from decimal import Decimal - if isinstance(number, Decimal): - number = int(number) - elif isinstance(number, float): - number = int(number) - elif isinstance(number, str): - number = int(float(number.replace(',', ''))) - - if number == 0: - return "صفر" - - if number < 0: - return "منفی " + number_to_persian_words(abs(number)) - - # اعداد یک رقمی - ones = [ - "", "یک", "دو", "سه", "چهار", "پنج", "شش", "هفت", "هشت", "نه" - ] - - # اعداد ده تا نوزده - teens = [ - "ده", "یازده", "دوازده", "سیزده", "چهارده", "پانزده", - "شانزده", "هفده", "هجده", "نوزده" - ] - - # اعداد بیست تا نود - tens = [ - "", "", "بیست", "سی", "چهل", "پنجاه", "شصت", "هفتاد", "هشتاد", "نود" - ] - - # اعداد صد تا نهصد - hundreds = [ - "", "یکصد", "دویست", "سیصد", "چهارصد", "پانصد", - "ششصد", "هفتصد", "هشتصد", "نهصد" - ] - - # مراتب بزرگتر - scale = [ - "", "هزار", "میلیون", "میلیارد", "بیلیون", "بیلیارد" - ] - - def convert_group(num): - """تبدیل گروه سه رقمی به حروف""" - if num == 0: - return "" - - result = [] - - # صدها - h = num // 100 - if h > 0: - result.append(hundreds[h]) - - # دهگان و یکان - remainder = num % 100 - - if remainder >= 10 and remainder < 20: - # اعداد 10 تا 19 - result.append(teens[remainder - 10]) - else: - # دهگان - t = remainder // 10 - if t > 0: - result.append(tens[t]) - - # یکان - o = remainder % 10 - if o > 0: - result.append(ones[o]) - - return " و ".join(result) - - # تقسیم عدد به گروه‌های سه رقمی - groups = [] - scale_index = 0 - - while number > 0: - group = number % 1000 - if group != 0: - group_text = convert_group(group) - if scale_index > 0: - group_text += " " + scale[scale_index] - groups.append(group_text) - - number //= 1000 - scale_index += 1 - - # معکوس کردن و ترکیب گروه‌ها - groups.reverse() - result = " و ".join(groups) - - return result - - except Exception: - return "" - - -def amount_to_persian_words(amount): - """ - تبدیل مبلغ به حروف فارسی با واحد ریال - مثال: 12345 -> دوازده هزار و سیصد و چهل و پنج ریال - """ - try: - words = number_to_persian_words(amount) - if words: - return words + " ریال" - return "" - except Exception: - return "" \ No newline at end of file + return f"{int(size_gb)} GB" if size_gb.is_integer() else f"{size_gb:.1f} GB" \ No newline at end of file diff --git a/accounts/templates/accounts/login.html b/accounts/templates/accounts/login.html index ecbb799..e43800d 100644 --- a/accounts/templates/accounts/login.html +++ b/accounts/templates/accounts/login.html @@ -27,8 +27,44 @@ layout-wide customizer-hide
- - logo + + + سامانه شفافیت
diff --git a/certificates/admin.py b/certificates/admin.py index f1eff8a..de9ba72 100644 --- a/certificates/admin.py +++ b/certificates/admin.py @@ -12,7 +12,9 @@ class CertificateTemplateAdmin(admin.ModelAdmin): @admin.register(CertificateInstance) class CertificateInstanceAdmin(admin.ModelAdmin): - list_display = ('process_instance', 'rendered_title', 'hologram_code', 'issued_at', 'approved') + list_display = ('process_instance', 'rendered_title', 'issued_at', 'approved') list_filter = ('approved', 'issued_at') - search_fields = ('process_instance__code', 'rendered_title', 'hologram_code') + search_fields = ('process_instance__code', 'rendered_title') autocomplete_fields = ('process_instance', 'template') + + diff --git a/certificates/migrations/0003_alter_certificateinstance_hologram_code.py b/certificates/migrations/0003_alter_certificateinstance_hologram_code.py deleted file mode 100644 index 00fb9cd..0000000 --- a/certificates/migrations/0003_alter_certificateinstance_hologram_code.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.4 on 2025-10-09 08:26 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('certificates', '0002_certificateinstance_hologram_code'), - ] - - operations = [ - migrations.AlterField( - model_name='certificateinstance', - name='hologram_code', - field=models.CharField(blank=True, max_length=50, null=True, unique=True, verbose_name='کد یکتا هولوگرام'), - ), - ] diff --git a/certificates/models.py b/certificates/models.py index c374035..a7afe72 100644 --- a/certificates/models.py +++ b/certificates/models.py @@ -28,7 +28,7 @@ class CertificateInstance(BaseModel): issued_at = models.DateField(auto_now_add=True, verbose_name='تاریخ صدور') approved = models.BooleanField(default=False, verbose_name='تایید شده') approved_at = models.DateTimeField(null=True, blank=True, verbose_name='تاریخ تایید') - hologram_code = models.CharField(max_length=50, null=True, blank=True, verbose_name='کد یکتا هولوگرام', unique=True) + hologram_code = models.CharField(max_length=50, null=True, blank=True, verbose_name='کد یکتا هولوگرام') class Meta: verbose_name = 'گواهی' diff --git a/certificates/templates/certificates/step.html b/certificates/templates/certificates/step.html index f8249cb..2f7e089 100644 --- a/certificates/templates/certificates/step.html +++ b/certificates/templates/certificates/step.html @@ -38,11 +38,9 @@
- {% if request.user|is_broker or request.user|is_manager %} - {% endif %} @@ -54,7 +52,7 @@
{% stepper_header instance step %}
- {% if request.user|is_broker or request.user|is_manager or request.user|is_water_resource_manager %} +
@@ -117,21 +115,6 @@
- {% else %} -
-
-
-
- -
-

دسترسی محدود

-

- متأسفانه شما دسترسی لازم برای مشاهده این صفحه را ندارید.
-

-
-
-
- {% endif %}
diff --git a/certificates/views.py b/certificates/views.py index 26a5d6f..5149334 100644 --- a/certificates/views.py +++ b/certificates/views.py @@ -6,14 +6,13 @@ from django.urls import reverse from django.utils import timezone from django.template import Template, Context from django.utils.safestring import mark_safe -from django.db import IntegrityError from processes.models import ProcessInstance, StepInstance from invoices.models import Invoice from installations.models import InstallationReport from .models import CertificateTemplate, CertificateInstance from common.consts import UserRoles -from common.decorators import allowed_roles + from _helpers.jalali import Gregorian from processes.utils import get_scoped_instance_or_404 @@ -151,7 +150,6 @@ def certificate_step(request, instance_id, step_id): @login_required -@allowed_roles([UserRoles.BROKER, UserRoles.MANAGER]) def certificate_print(request, instance_id): instance = get_scoped_instance_or_404(request, instance_id) cert = CertificateInstance.objects.filter(process_instance=instance).order_by('-created').first() @@ -159,56 +157,15 @@ def certificate_print(request, instance_id): if request.method == 'POST': # Save/update hologram code then print code = (request.POST.get('hologram_code') or '').strip() - - if not code: - messages.error(request, 'کد یکتای هولوگرام الزامی است') - # Find certificate step to redirect back - certificate_step = instance.process.steps.filter(order=9).first() - if certificate_step and instance.current_step: - return redirect('processes:step_detail', instance_id=instance.id, step_id=certificate_step.id) - return redirect('processes:instance_summary', instance_id=instance.id) - - try: - if cert: - # Check if hologram code is already used by another certificate - if CertificateInstance.objects.filter(hologram_code=code).exclude(id=cert.id).exists(): - messages.error(request, 'این کد هولوگرام قبلاً استفاده شده است. لطفاً کد دیگری وارد کنید') - # Find certificate step to redirect back - certificate_step = instance.process.steps.filter(order=9).first() - if certificate_step and instance.current_step: - return redirect('processes:step_detail', instance_id=instance.id, step_id=certificate_step.id) - return redirect('processes:instance_summary', instance_id=instance.id) - + if cert: + if code: cert.hologram_code = code cert.save(update_fields=['hologram_code']) - else: - # Check if hologram code is already used - if CertificateInstance.objects.filter(hologram_code=code).exists(): - messages.error(request, 'این کد هولوگرام قبلاً استفاده شده است. لطفاً کد دیگری وارد کنید') - # Find certificate step to redirect back - certificate_step = instance.process.steps.filter(order=9).first() - if certificate_step and instance.current_step: - return redirect('processes:step_detail', instance_id=instance.id, step_id=certificate_step.id) - return redirect('processes:instance_summary', instance_id=instance.id) - - template = CertificateTemplate.objects.filter(is_active=True).order_by('-created').first() - if template: - title, body = _render_template(template, instance) - cert = CertificateInstance.objects.create( - process_instance=instance, - template=template, - rendered_title=title, - rendered_body=body, - hologram_code=code - ) - except IntegrityError: - messages.error(request, 'این کد هولوگرام قبلاً استفاده شده است. لطفاً کد دیگری وارد کنید') - # Find certificate step to redirect back - certificate_step = instance.process.steps.filter(order=9).first() - if certificate_step and instance.current_step: - return redirect('processes:step_detail', instance_id=instance.id, step_id=certificate_step.id) - return redirect('processes:instance_summary', instance_id=instance.id) - + else: + template = CertificateTemplate.objects.filter(is_active=True).order_by('-created').first() + if template: + title, body = _render_template(template, instance) + cert = CertificateInstance.objects.create(process_instance=instance, template=template, rendered_title=title, rendered_body=body, hologram_code=code or None) # proceed to rendering page after saving code return render(request, 'certificates/print.html', { 'instance': instance, diff --git a/common/templatetags/common_tags.py b/common/templatetags/common_tags.py index 2373852..ef17d42 100644 --- a/common/templatetags/common_tags.py +++ b/common/templatetags/common_tags.py @@ -1,23 +1,7 @@ from django import template -from _helpers.utils import jalali_converter2, amount_to_persian_words +from _helpers.utils import jalali_converter2 register = template.Library() @register.filter(name='to_jalali') def to_jalali(value): - return jalali_converter2(value) - - -@register.filter(name='amount_to_words') -def amount_to_words(value): - """تبدیل مبلغ به حروف فارسی""" - try: - if value is None or value == '': - return "" - # تبدیل Decimal به int - from decimal import Decimal - if isinstance(value, Decimal): - value = int(value) - result = amount_to_persian_words(value) - return result if result else "صفر ریال" - except Exception as e: - return f"خطا: {str(e)}" \ No newline at end of file + return jalali_converter2(value) \ No newline at end of file diff --git a/installations/forms.py b/installations/forms.py index b8be2b0..e877b2b 100644 --- a/installations/forms.py +++ b/installations/forms.py @@ -66,11 +66,13 @@ class InstallationReportForm(forms.ModelForm): 'class': 'form-select' }, choices=[ ('', 'انتخاب کنید'), - ('direct', 'مستقیم'), - ('indirect', 'غیرمستقیم') + ('A', 'A'), + ('B', 'B') ]), 'discharge_pipe_diameter': forms.NumberInput(attrs={ 'class': 'form-control', + 'min': '0', + 'step': '1', 'required': True }), 'usage_type': forms.Select(attrs={ @@ -88,18 +90,20 @@ class InstallationReportForm(forms.ModelForm): }), 'motor_power': forms.NumberInput(attrs={ 'class': 'form-control', + 'min': '0', + 'step': '1', 'required': True }), 'pre_calibration_flow_rate': forms.NumberInput(attrs={ 'class': 'form-control', 'min': '0', - 'step': '0.0001', + 'step': '0.01', 'required': True }), 'post_calibration_flow_rate': forms.NumberInput(attrs={ 'class': 'form-control', 'min': '0', - 'step': '0.0001', + 'step': '0.01', 'required': True }), 'water_meter_manufacturer': forms.Select(attrs={ diff --git a/installations/migrations/0008_alter_installationreport_post_calibration_flow_rate_and_more.py b/installations/migrations/0008_alter_installationreport_post_calibration_flow_rate_and_more.py deleted file mode 100644 index b69ada5..0000000 --- a/installations/migrations/0008_alter_installationreport_post_calibration_flow_rate_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.2.4 on 2025-10-09 08:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('installations', '0007_installationreport_meter_model'), - ] - - operations = [ - migrations.AlterField( - model_name='installationreport', - name='post_calibration_flow_rate', - field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True, verbose_name='(لیتر بر ثانیه)دبی بعد از کالیبراسیون'), - ), - migrations.AlterField( - model_name='installationreport', - name='pre_calibration_flow_rate', - field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True, verbose_name='(لیتر بر ثانیه)دبی قبل از کالیبراسیون'), - ), - ] diff --git a/installations/migrations/0009_alter_installationreport_meter_model_and_more.py b/installations/migrations/0009_alter_installationreport_meter_model_and_more.py deleted file mode 100644 index e5e3e8b..0000000 --- a/installations/migrations/0009_alter_installationreport_meter_model_and_more.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 5.2.4 on 2025-10-09 12:28 - -import django.core.validators -from decimal import Decimal -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('installations', '0008_alter_installationreport_post_calibration_flow_rate_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='installationreport', - name='meter_model', - field=models.CharField(blank=True, choices=[('direct', 'مستقیم'), ('indirect', 'غیرمستقیم')], max_length=20, null=True, verbose_name='مدل کنتور'), - ), - migrations.AlterField( - model_name='installationreport', - name='post_calibration_flow_rate', - field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True, validators=[django.core.validators.MinValueValidator(Decimal('0.0001'))], verbose_name='(لیتر بر ثانیه)دبی بعد از کالیبراسیون'), - ), - migrations.AlterField( - model_name='installationreport', - name='pre_calibration_flow_rate', - field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True, validators=[django.core.validators.MinValueValidator(Decimal('0.0001'))], verbose_name='(لیتر بر ثانیه)دبی قبل از کالیبراسیون'), - ), - ] diff --git a/installations/migrations/0010_alter_installationreport_motor_power_and_more.py b/installations/migrations/0010_alter_installationreport_motor_power_and_more.py deleted file mode 100644 index 3cc7f90..0000000 --- a/installations/migrations/0010_alter_installationreport_motor_power_and_more.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 5.2.4 on 2025-10-09 12:31 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('installations', '0009_alter_installationreport_meter_model_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='installationreport', - name='motor_power', - field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True, verbose_name='(کیلووات ساعت) قدرت موتور'), - ), - migrations.AlterField( - model_name='installationreport', - name='post_calibration_flow_rate', - field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True, verbose_name='(لیتر بر ثانیه)دبی بعد از کالیبراسیون'), - ), - migrations.AlterField( - model_name='installationreport', - name='pre_calibration_flow_rate', - field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True, verbose_name='(لیتر بر ثانیه)دبی قبل از کالیبراسیون'), - ), - ] diff --git a/installations/migrations/0011_alter_installationreport_discharge_pipe_diameter.py b/installations/migrations/0011_alter_installationreport_discharge_pipe_diameter.py deleted file mode 100644 index ae5e2bb..0000000 --- a/installations/migrations/0011_alter_installationreport_discharge_pipe_diameter.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.4 on 2025-10-09 12:33 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('installations', '0010_alter_installationreport_motor_power_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='installationreport', - name='discharge_pipe_diameter', - field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True, verbose_name='قطر لوله آبده (اینچ)'), - ), - ] diff --git a/installations/models.py b/installations/models.py index c7d7e31..6cd48bd 100644 --- a/installations/models.py +++ b/installations/models.py @@ -1,8 +1,6 @@ from django.db import models from django.contrib.auth import get_user_model from django.utils import timezone -from django.core.validators import MinValueValidator -from decimal import Decimal from common.models import BaseModel User = get_user_model() @@ -50,12 +48,12 @@ class InstallationReport(BaseModel): ] meter_type = models.CharField(max_length=20, choices=METER_TYPE_CHOICES, null=True, blank=True, verbose_name='نوع کنتور') METER_MODEL_CHOICES = [ - ('direct', 'مستقیم'), - ('indirect', 'غیرمستقیم'), + ('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.DecimalField(max_digits=10, decimal_places=4, null=True, blank=True, verbose_name='قطر لوله آبده (اینچ)') + discharge_pipe_diameter = models.PositiveIntegerField(null=True, blank=True, verbose_name='قطر لوله آبده (اینچ)') USAGE_TYPE_CHOICES = [ ('domestic', 'شرب و خدمات'), ('agriculture', 'کشاورزی'), @@ -63,9 +61,9 @@ class InstallationReport(BaseModel): ] usage_type = models.CharField(max_length=20, choices=USAGE_TYPE_CHOICES, null=True, verbose_name='نوع مصرف') exploitation_license_number = models.CharField(max_length=50, verbose_name='شماره پروانه بهره‌برداری چاه') - motor_power = models.DecimalField(max_digits=10, decimal_places=4, null=True, blank=True, verbose_name='(کیلووات ساعت) قدرت موتور') - pre_calibration_flow_rate = models.DecimalField(max_digits=10, decimal_places=4, null=True, blank=True, verbose_name='(لیتر بر ثانیه)دبی قبل از کالیبراسیون') - post_calibration_flow_rate = models.DecimalField(max_digits=10, decimal_places=4, null=True, blank=True, verbose_name='(لیتر بر ثانیه)دبی بعد از کالیبراسیون') + motor_power = models.PositiveIntegerField(null=True, blank=True, verbose_name='(کیلووات ساعت) قدرت موتور') + pre_calibration_flow_rate = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='(لیتر بر ثانیه)دبی قبل از کالیبراسیون') + post_calibration_flow_rate = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='(لیتر بر ثانیه)دبی بعد از کالیبراسیون') water_meter_manufacturer = models.ForeignKey('wells.WaterMeterManufacturer', on_delete=models.SET_NULL, null=True, blank=True, verbose_name='شرکت سازنده کنتور آب') sim_number = models.CharField(max_length=20, null=True, blank=True, verbose_name='شماره سیمکارت') driving_force = models.CharField(max_length=50, null=True, blank=True, verbose_name='نیرو محرکه چاه') diff --git a/installations/templates/installations/installation_report_step.html b/installations/templates/installations/installation_report_step.html index 3246dea..2d14128 100644 --- a/installations/templates/installations/installation_report_step.html +++ b/installations/templates/installations/installation_report_step.html @@ -31,60 +31,6 @@ .removal-checkbox:checked:focus { box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25) !important; } - - /* Upload Loader Overlay */ - #uploadLoader { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.7); - z-index: 9999; - display: none; - justify-content: center; - align-items: center; - } - - #uploadLoader.active { - display: flex; - } - - .loader-content { - background: white; - padding: 2rem; - border-radius: 12px; - text-align: center; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); - max-width: 300px; - } - - .loader-spinner { - width: 50px; - height: 50px; - border: 5px solid #f3f3f3; - border-top: 5px solid #696cff; - border-radius: 50%; - animation: spin 1s linear infinite; - margin: 0 auto 1rem; - } - - @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } - } - - .loader-text { - font-size: 1.1rem; - font-weight: 500; - color: #333; - margin-bottom: 0.5rem; - } - - .loader-subtext { - font-size: 0.9rem; - color: #666; - } {% endblock %} @@ -92,15 +38,6 @@ {% include '_toasts.html' %} - -
-
-
-
در حال آپلود...
-
لطفا تا بارگذاری کامل گزارش منتظر بمانید.
-
-
- {% instance_info_modal instance %} @@ -579,7 +516,7 @@ {% if user_is_installer %} {% endif %} - {% if next_step and not edit_mode and report %} + {% if next_step and not edit_mode %}
بعدی @@ -701,9 +638,6 @@ // Require date and show success toast on submit (persist across redirect) (function(){ const form = document.querySelector('form[enctype]') || document.querySelector('form'); - const loader = document.getElementById('uploadLoader'); - const submitButton = document.querySelector('button[type="submit"][form="installation-report-form"]'); - if (!form) return; form.addEventListener('submit', function(ev){ const display = document.getElementById('id_visited_date_display'); @@ -729,32 +663,8 @@ return false; } } catch(_) {} - - // Show loader overlay when form is valid and submitting - if (loader) { - loader.classList.add('active'); - } - - // Disable submit button to prevent double submission - if (submitButton) { - submitButton.disabled = true; - submitButton.innerHTML = 'در حال ارسال...'; - } - try { sessionStorage.setItem('install_report_saved', '1'); } catch(_) {} }, false); - - // Hide loader on back navigation or page show (in case of errors) - window.addEventListener('pageshow', function(event) { - if (loader) { - loader.classList.remove('active'); - } - if (submitButton) { - submitButton.disabled = false; - submitButton.innerHTML = 'ثبت گزارش'; - } - }); - // on load, if saved flag exists, show toast try { if (sessionStorage.getItem('install_report_saved') === '1') { diff --git a/invoices/admin.py b/invoices/admin.py index ed82d80..a53e692 100644 --- a/invoices/admin.py +++ b/invoices/admin.py @@ -63,9 +63,7 @@ class InvoiceAdmin(SimpleHistoryAdmin): def remaining_amount_display(self, obj): amount = obj.get_remaining_amount() color = "green" if amount <= 0 else "red" - # Pre-format the number to avoid applying a numeric format code to a SafeString - formatted_amount = f"{amount:,.0f}" - return format_html('{} ریال', color, formatted_amount) + return format_html('{:,.0f} ریال', color, amount) remaining_amount_display.short_description = "مبلغ باقی‌مانده" @admin.register(Payment) diff --git a/invoices/migrations/0003_historicalpayment_payment_stage_and_more.py b/invoices/migrations/0003_historicalpayment_payment_stage_and_more.py deleted file mode 100644 index f0c30bb..0000000 --- a/invoices/migrations/0003_historicalpayment_payment_stage_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.2.4 on 2025-10-09 10:59 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('invoices', '0002_remove_historicalinvoice_paid_amount_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='historicalpayment', - name='payment_stage', - field=models.CharField(choices=[('quote', 'پیش\u200cفاکتور'), ('final_settlement', 'تسویه نهایی')], default='quote', max_length=20, verbose_name='مرحله پرداخت'), - ), - migrations.AddField( - model_name='payment', - name='payment_stage', - field=models.CharField(choices=[('quote', 'پیش\u200cفاکتور'), ('final_settlement', 'تسویه نهایی')], default='quote', max_length=20, verbose_name='مرحله پرداخت'), - ), - ] diff --git a/invoices/models.py b/invoices/models.py index b61d487..b93e4b4 100644 --- a/invoices/models.py +++ b/invoices/models.py @@ -350,11 +350,6 @@ class InvoiceItem(BaseModel): class Payment(BaseModel): """مدل پرداخت‌ها""" - PAYMENT_STAGE_CHOICES = [ - ('quote', 'پیش‌فاکتور'), - ('final_settlement', 'تسویه نهایی'), - ] - invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE, related_name='payments', verbose_name="فاکتور") amount = models.DecimalField(max_digits=15, decimal_places=2, verbose_name="مبلغ پرداخت") direction = models.CharField( @@ -375,12 +370,6 @@ class Payment(BaseModel): default='cash', verbose_name="روش پرداخت" ) - payment_stage = models.CharField( - max_length=20, - choices=PAYMENT_STAGE_CHOICES, - default='quote', - verbose_name="مرحله پرداخت" - ) reference_number = models.CharField(max_length=100, verbose_name="شماره مرجع", blank=True, unique=True) payment_date = models.DateField(verbose_name="تاریخ پرداخت") notes = models.TextField(verbose_name="یادداشت‌ها", blank=True) diff --git a/invoices/templates/invoices/final_invoice_print.html b/invoices/templates/invoices/final_invoice_print.html index 167306f..36e23f0 100644 --- a/invoices/templates/invoices/final_invoice_print.html +++ b/invoices/templates/invoices/final_invoice_print.html @@ -7,7 +7,6 @@ {% load static %} {% load humanize %} - {% load common_tags %} @@ -49,38 +48,71 @@
- +
-
-
فاکتور
-
-
-
شماره : {{ instance.code }}
-
تاریخ صدور: {{ invoice.jcreated_date }}
-
+
+
+
+ {% if instance.broker.company and instance.broker.company.logo %} + لوگو + {% else %} + + {% endif %}
+
+ {% if instance.broker.company %} + {{ instance.broker.company.name }} + {% endif %} + {% if instance.broker.company %} +
+ {% if instance.broker.company.address %} +
{{ instance.broker.company.address }}
+ {% endif %} + {% if instance.broker.affairs.county.city.name %} +
{{ instance.broker.affairs.county.city.name }}، ایران
+ {% endif %} + {% if instance.broker.company.phone %} +
تلفن: {{ instance.broker.company.phone }}
+ {% endif %} +
+ {% endif %} +
+
+
+
+
#فاکتور نهایی {{ instance.code }}
+
تاریخ صدور: {{ invoice.jcreated_date }}
+
+
-
- +
+
اطلاعات مشترک {% if instance.representative.profile and instance.representative.profile.user_type == 'legal' %}(حقوقی){% else %}(حقیقی){% endif %}
-
شماره اشتراک آب: {{ instance.well.water_subscription_number }}
{% if instance.representative.profile and instance.representative.profile.user_type == 'legal' %} -
نام شرکت: {{ instance.representative.profile.company_name|default:"-" }}
-
شناسه ملی: {{ instance.representative.profile.company_national_id|default:"-" }}
+
نام شرکت: {{ instance.representative.profile.company_name|default:"-" }}
+
شناسه ملی: {{ instance.representative.profile.company_national_id|default:"-" }}
{% endif %} -
نام و نام خانوادگی: {{ invoice.customer.get_full_name|default:instance.representative.get_full_name }}
+
نام: {{ invoice.customer.get_full_name|default:instance.representative.get_full_name }}
{% if instance.representative.profile and instance.representative.profile.national_code %} -
کد ملی: {{ instance.representative.profile.national_code }}
+
کد ملی: {{ instance.representative.profile.national_code }}
{% endif %} {% if instance.representative.profile and instance.representative.profile.phone_number_1 %} -
تلفن: {{ instance.representative.profile.phone_number_1 }}
+
تلفن: {{ instance.representative.profile.phone_number_1 }}
{% endif %} {% if instance.representative.profile and instance.representative.profile.address %} -
آدرس: {{ instance.representative.profile.address }}
+
آدرس: {{ instance.representative.profile.address }}
{% endif %} +
+
+
اطلاعات چاه
+
شماره اشتراک آب: {{ instance.well.water_subscription_number }}
+
شماره اشتراک برق: {{ instance.well.electricity_subscription_number|default:"-" }}
+
سریال کنتور: {{ instance.well.water_meter_serial_number|default:"-" }}
+
قدرت چاه: {{ instance.well.well_power|default:"-" }}
+
@@ -112,43 +144,47 @@ - جمع کل(ریال): - {{ invoice.total_amount|floatformat:0|intcomma:False }} + جمع کل(ریال): + {{ invoice.total_amount|floatformat:0|intcomma:False }} {% if invoice.discount_amount > 0 %} - تخفیف(ریال): - {{ invoice.discount_amount|floatformat:0|intcomma:False }} + تخفیف(ریال): + {{ invoice.discount_amount|floatformat:0|intcomma:False }} {% endif %} - مالیات بر ارزش افزوده(ریال): - {{ 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 }} - - مبلغ نهایی به حروف: - {{ invoice.final_amount|amount_to_words }} -
- {% if instance.broker.company %}
+
مهر و امضا:
+
    + {% if instance.broker.company and instance.broker.company.signature %} +
  • امضا
  • + {% endif %} +
+
+ {% if instance.broker.company %} +
اطلاعات پرداخت
{% if instance.broker.company.card_number %}
شماره کارت: {{ instance.broker.company.card_number }}
@@ -164,20 +200,6 @@ {% endif %}
{% endif %} - -
- {% if instance.broker.company and instance.broker.company.signature %} -
-
مهر و امضا - {% if instance.broker.company.signature %} - امضای شرکت - {% endif %} -
- -
- {% endif %} -
-
diff --git a/invoices/templates/invoices/final_settlement_step.html b/invoices/templates/invoices/final_settlement_step.html index 97a3861..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 and needs_approval %} + {% if is_broker and invoice.get_remaining_amount != 0 %}
ثبت تراکنش تسویه
@@ -193,7 +193,7 @@
- {% if approver_statuses and needs_approval and step_instance.status != 'completed' %} + {% if approver_statuses and invoice.get_remaining_amount != 0 and step_instance.status != 'completed' %}
وضعیت تاییدها
@@ -318,11 +318,7 @@