diff --git a/_base/settings.py b/_base/settings.py
index e53a6f1..8261409 100644
--- a/_base/settings.py
+++ b/_base/settings.py
@@ -167,7 +167,7 @@ JAZZMIN_SETTINGS = {
# Copyright on the footer
"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",
+ # "site_logo": "../static/dist/img/iconlogo.png",
# Relative paths to custom CSS/JS scripts (must be present in static files)
"custom_css": "../static/admin/css/custom_rtl.css",
"custom_js": None,
diff --git a/accounts/migrations/0001_initial.py b/accounts/migrations/0001_initial.py
index de431d4..7b35fb7 100644
--- a/accounts/migrations/0001_initial.py
+++ b/accounts/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.2.4 on 2025-08-14 09:02
+# Generated by Django 5.2.4 on 2025-09-07 07:35
import django.core.validators
import django.db.models.deletion
@@ -17,6 +17,27 @@ class Migration(migrations.Migration):
]
operations = [
+ migrations.CreateModel(
+ name='Company',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created', models.DateTimeField(auto_now_add=True, verbose_name='تاریخ ایجاد')),
+ ('updated', models.DateTimeField(auto_now=True, verbose_name='تاریخ بروزرسانی')),
+ ('is_active', models.BooleanField(default=True, verbose_name='فعال')),
+ ('is_deleted', models.BooleanField(default=False, verbose_name='حذف شده')),
+ ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')),
+ ('slug', models.SlugField(max_length=100, unique=True, verbose_name='اسلاگ')),
+ ('name', models.CharField(max_length=100, verbose_name='نام')),
+ ('logo', models.ImageField(blank=True, null=True, upload_to='companies/logos', verbose_name='لوگوی شرکت')),
+ ('signature', models.ImageField(blank=True, null=True, upload_to='companies/signatures', verbose_name='امضای شرکت')),
+ ('address', models.TextField(blank=True, null=True, verbose_name='آدرس')),
+ ('phone', models.CharField(blank=True, max_length=11, null=True, verbose_name='شماره تماس')),
+ ],
+ options={
+ 'verbose_name': 'شرکت',
+ 'verbose_name_plural': 'شرکت\u200cها',
+ },
+ ),
migrations.CreateModel(
name='HistoricalProfile',
fields=[
@@ -30,6 +51,7 @@ class Migration(migrations.Migration):
('address', models.TextField(blank=True, null=True, verbose_name='آدرس')),
('card_number', models.CharField(blank=True, max_length=16, null=True, validators=[django.core.validators.RegexValidator(code='invalid_card_number', message='شماره کارت باید فقط شامل اعداد باشد.', regex='^\\d+$')], verbose_name='شماره کارت')),
('account_number', models.CharField(blank=True, max_length=20, null=True, validators=[django.core.validators.RegexValidator(code='invalid_account_number', message='شماره حساب باید فقط شامل اعداد باشد.', regex='^\\d+$')], verbose_name='شماره حساب')),
+ ('bank_name', models.CharField(blank=True, choices=[('mellat', 'بانک ملت'), ('saman', 'بانک سامان'), ('parsian', 'بانک پارسیان'), ('sina', 'بانک سینا'), ('tejarat', 'بانک تجارت'), ('tosee', 'بانک توسعه'), ('iran_zamin', 'بانک ایران زمین'), ('meli', 'بانک ملی'), ('saderat', 'بانک توسعه صادرات'), ('iran_zamin', 'بانک ایران زمین'), ('refah', 'بانک رفاه'), ('eghtesad_novin', 'بانک اقتصاد نوین'), ('pasargad', 'بانک پاسارگاد'), ('other', 'سایر')], max_length=255, null=True, verbose_name='نام بانک')),
('phone_number_1', models.CharField(blank=True, max_length=11, null=True, verbose_name='شماره تماس ۱')),
('phone_number_2', models.CharField(blank=True, max_length=11, null=True, verbose_name='شماره تماس ۲')),
('pic', models.TextField(default='../static/sample_images/profile.jpg', max_length=100, verbose_name='تصویر')),
@@ -84,6 +106,7 @@ class Migration(migrations.Migration):
('address', models.TextField(blank=True, null=True, verbose_name='آدرس')),
('card_number', models.CharField(blank=True, max_length=16, null=True, validators=[django.core.validators.RegexValidator(code='invalid_card_number', message='شماره کارت باید فقط شامل اعداد باشد.', regex='^\\d+$')], verbose_name='شماره کارت')),
('account_number', models.CharField(blank=True, max_length=20, null=True, validators=[django.core.validators.RegexValidator(code='invalid_account_number', message='شماره حساب باید فقط شامل اعداد باشد.', regex='^\\d+$')], verbose_name='شماره حساب')),
+ ('bank_name', models.CharField(blank=True, choices=[('mellat', 'بانک ملت'), ('saman', 'بانک سامان'), ('parsian', 'بانک پارسیان'), ('sina', 'بانک سینا'), ('tejarat', 'بانک تجارت'), ('tosee', 'بانک توسعه'), ('iran_zamin', 'بانک ایران زمین'), ('meli', 'بانک ملی'), ('saderat', 'بانک توسعه صادرات'), ('iran_zamin', 'بانک ایران زمین'), ('refah', 'بانک رفاه'), ('eghtesad_novin', 'بانک اقتصاد نوین'), ('pasargad', 'بانک پاسارگاد'), ('other', 'سایر')], max_length=255, null=True, verbose_name='نام بانک')),
('phone_number_1', models.CharField(blank=True, max_length=11, null=True, verbose_name='شماره تماس ۱')),
('phone_number_2', models.CharField(blank=True, max_length=11, null=True, verbose_name='شماره تماس ۲')),
('pic', models.ImageField(default='../static/sample_images/profile.jpg', upload_to='profile_images', verbose_name='تصویر')),
diff --git a/accounts/migrations/0002_company.py b/accounts/migrations/0002_company.py
deleted file mode 100644
index c944cdf..0000000
--- a/accounts/migrations/0002_company.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# Generated by Django 5.2.4 on 2025-08-21 06:33
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('accounts', '0001_initial'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='Company',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('created', models.DateTimeField(auto_now_add=True, verbose_name='تاریخ ایجاد')),
- ('updated', models.DateTimeField(auto_now=True, verbose_name='تاریخ بروزرسانی')),
- ('is_active', models.BooleanField(default=True, verbose_name='فعال')),
- ('is_deleted', models.BooleanField(default=False, verbose_name='حذف شده')),
- ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')),
- ('slug', models.SlugField(max_length=100, unique=True, verbose_name='اسلاگ')),
- ('name', models.CharField(max_length=100, verbose_name='نام')),
- ('logo', models.ImageField(blank=True, null=True, upload_to='companies/logos', verbose_name='لوگوی شرکت')),
- ('signature', models.ImageField(blank=True, null=True, upload_to='companies/signatures', verbose_name='امضای شرکت')),
- ('address', models.TextField(blank=True, null=True, verbose_name='آدرس')),
- ('phone', models.CharField(blank=True, max_length=11, null=True, verbose_name='شماره تماس')),
- ],
- options={
- 'verbose_name': 'شرکت',
- 'verbose_name_plural': 'شرکت\u200cها',
- },
- ),
- ]
diff --git a/accounts/migrations/0003_historicalprofile_bank_name_profile_bank_name.py b/accounts/migrations/0003_historicalprofile_bank_name_profile_bank_name.py
deleted file mode 100644
index 6becfec..0000000
--- a/accounts/migrations/0003_historicalprofile_bank_name_profile_bank_name.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Generated by Django 5.2.4 on 2025-08-21 07:06
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('accounts', '0002_company'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='historicalprofile',
- name='bank_name',
- field=models.CharField(blank=True, choices=[('mellat', 'بانک ملت'), ('saman', 'بانک سامان'), ('parsian', 'بانک پارسیان'), ('sina', 'بانک سینا'), ('tejarat', 'بانک تجارت'), ('tosee', 'بانک توسعه'), ('iran_zamin', 'بانک ایران زمین'), ('meli', 'بانک ملی'), ('saderat', 'بانک توسعه صادرات'), ('iran_zamin', 'بانک ایران زمین'), ('refah', 'بانک رفاه'), ('eghtesad_novin', 'بانک اقتصاد نوین'), ('pasargad', 'بانک پاسارگاد'), ('other', 'سایر')], max_length=255, null=True, verbose_name='نام بانک'),
- ),
- migrations.AddField(
- model_name='profile',
- name='bank_name',
- field=models.CharField(blank=True, choices=[('mellat', 'بانک ملت'), ('saman', 'بانک سامان'), ('parsian', 'بانک پارسیان'), ('sina', 'بانک سینا'), ('tejarat', 'بانک تجارت'), ('tosee', 'بانک توسعه'), ('iran_zamin', 'بانک ایران زمین'), ('meli', 'بانک ملی'), ('saderat', 'بانک توسعه صادرات'), ('iran_zamin', 'بانک ایران زمین'), ('refah', 'بانک رفاه'), ('eghtesad_novin', 'بانک اقتصاد نوین'), ('pasargad', 'بانک پاسارگاد'), ('other', 'سایر')], max_length=255, null=True, verbose_name='نام بانک'),
- ),
- ]
diff --git a/accounts/templates/accounts/login.html b/accounts/templates/accounts/login.html
index 67f300d..e5791f3 100644
--- a/accounts/templates/accounts/login.html
+++ b/accounts/templates/accounts/login.html
@@ -64,21 +64,18 @@ layout-wide customizer-hide
سامانه شفافیت
-
-
Welcome to Sneat! 👋
- Please sign-in to your account and start the adventure
-
- New on our platform?
-
- Create an account
-
-
-
-
-
-
diff --git a/accounts/urls.py b/accounts/urls.py
index 8352a63..ac5119d 100644
--- a/accounts/urls.py
+++ b/accounts/urls.py
@@ -1,10 +1,11 @@
from django.urls import path
-from accounts.views import login_view, dashboard, customer_list, add_customer_ajax, edit_customer_ajax, get_customer_data
+from accounts.views import login_view, dashboard, customer_list, add_customer_ajax, edit_customer_ajax, get_customer_data, logout_view
app_name = "accounts"
urlpatterns = [
path('login/', login_view, name='login'),
+ path('logout/', logout_view, name='logout'),
path('dashboard/', dashboard, name='dashboard'),
path('customers/', customer_list, name='customer_list'),
path('customers/add/', add_customer_ajax, name='add_customer_ajax'),
diff --git a/accounts/views.py b/accounts/views.py
index b238b5e..6a4f23c 100644
--- a/accounts/views.py
+++ b/accounts/views.py
@@ -1,11 +1,11 @@
from django.contrib import messages
-from django.contrib.auth import login, authenticate
+from django.contrib.auth import login, authenticate, logout
from django.shortcuts import render, redirect, get_object_or_404
from django.http import JsonResponse
from django.views.decorators.http import require_POST, require_GET
from django.views.decorators.csrf import csrf_exempt
from django import forms
-
+from django.contrib.auth.decorators import login_required
from accounts.models import Profile
from accounts.forms import CustomerForm
from common.consts import UserRoles
@@ -21,15 +21,12 @@ def login_view(request):
username = request.POST.get("username")
password = request.POST.get("password")
user = authenticate(request, username=username, password=password)
- # if user is not None:
- # login(request, user)
- # if user.profile.has_none_of([UserRoles.MANAGER]):
- # return redirect("dashboard:dashboard")
- # else:
- # return redirect("dashboard:admin_dashboard")
- # else:
- # messages.error(request, "کاربری با این مشخصات یافت نشد!")
- # return redirect("accounts:login")
+ if user is not None:
+ login(request, user)
+ return redirect("processes:request_list")
+ else:
+ messages.error(request, "کاربری با این مشخصات یافت نشد!")
+ return redirect("accounts:login")
return render(request, "accounts/login.html")
@@ -37,7 +34,7 @@ def dashboard(request):
return render(request, "accounts/dashboard.html")
-
+@login_required
def customer_list(request):
# Get all profiles that have customer role
customers = Profile.objects.filter(roles__slug=UserRoles.CUSTOMER.value, is_deleted=False).select_related('user')
@@ -163,3 +160,9 @@ def get_customer_data(request, customer_id):
},
'form_html': form_html
})
+
+
+def logout_view(request):
+ """Log out current user and redirect to login page."""
+ logout(request)
+ return redirect("accounts:login")
diff --git a/certificates/migrations/0001_initial.py b/certificates/migrations/0001_initial.py
index 83533bd..b5753ec 100644
--- a/certificates/migrations/0001_initial.py
+++ b/certificates/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.2.4 on 2025-08-22 09:58
+# Generated by Django 5.2.4 on 2025-09-07 07:35
import django.db.models.deletion
from django.db import migrations, models
@@ -9,6 +9,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
+ ('accounts', '0001_initial'),
('processes', '0001_initial'),
]
@@ -23,10 +24,8 @@ class Migration(migrations.Migration):
('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')),
('title', models.CharField(max_length=200, verbose_name='عنوان')),
('body', models.TextField(verbose_name='متن قالب (با جایگزین\u200cها)')),
- ('company_logo', models.ImageField(blank=True, null=True, upload_to='certificates/logos/%Y/%m/%d/', verbose_name='لوگو')),
- ('company_name', models.CharField(blank=True, max_length=200, verbose_name='نام شرکت')),
- ('company_seal_signature', models.ImageField(blank=True, null=True, upload_to='certificates/seals/%Y/%m/%d/', verbose_name='مهر و امضا')),
('is_active', models.BooleanField(default=True, verbose_name='فعال')),
+ ('company', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.company', verbose_name='شرکت صادر کننده')),
],
options={
'verbose_name': 'قالب گواهی',
diff --git a/certificates/migrations/0002_remove_certificatetemplate_company_logo_and_more.py b/certificates/migrations/0002_remove_certificatetemplate_company_logo_and_more.py
deleted file mode 100644
index e929c5a..0000000
--- a/certificates/migrations/0002_remove_certificatetemplate_company_logo_and_more.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# Generated by Django 5.2.4 on 2025-08-22 10:05
-
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('accounts', '0003_historicalprofile_bank_name_profile_bank_name'),
- ('certificates', '0001_initial'),
- ]
-
- operations = [
- migrations.RemoveField(
- model_name='certificatetemplate',
- name='company_logo',
- ),
- migrations.RemoveField(
- model_name='certificatetemplate',
- name='company_name',
- ),
- migrations.RemoveField(
- model_name='certificatetemplate',
- name='company_seal_signature',
- ),
- migrations.AddField(
- model_name='certificatetemplate',
- name='company',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.company', verbose_name='شرکت صادر کننده'),
- ),
- ]
diff --git a/certificates/templates/certificates/step.html b/certificates/templates/certificates/step.html
index 3392f82..b8923c2 100644
--- a/certificates/templates/certificates/step.html
+++ b/certificates/templates/certificates/step.html
@@ -2,6 +2,7 @@
{% load static %}
{% load processes_tags %}
{% load humanize %}
+ {% load accounts_tags %}
{% block sidebar %}
{% include 'sidebars/admin.html' %}
@@ -79,7 +80,11 @@
{% else %}{% endif %}
diff --git a/certificates/views.py b/certificates/views.py
index 428c5ba..761ee83 100644
--- a/certificates/views.py
+++ b/certificates/views.py
@@ -9,6 +9,7 @@ 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 _helpers.jalali import Gregorian
@@ -78,6 +79,14 @@ def certificate_step(request, instance_id, step_id):
next_step = instance.process.steps.filter(order__gt=instance.current_step.order).first() if instance.current_step else None
if request.method == 'POST':
+ # Only broker can approve and finish certificate step
+ try:
+ if not (hasattr(request.user, 'profile') and request.user.profile.has_role(UserRoles.BROKER)):
+ messages.error(request, 'شما مجوز تایید این مرحله را ندارید')
+ return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id)
+ except Exception:
+ messages.error(request, 'شما مجوز تایید این مرحله را ندارید')
+ return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id)
cert.approved = True
cert.approved_at = timezone.now()
cert.save()
@@ -89,7 +98,10 @@ def certificate_step(request, instance_id, step_id):
instance.current_step = next_step
instance.save()
return redirect('processes:step_detail', instance_id=instance.id, step_id=next_step.id)
- return redirect('processes:request_list')
+ # Mark the whole process instance as completed on the last step
+ instance.status = 'completed'
+ instance.save()
+ return redirect('processes:instance_summary', instance_id=instance.id)
return render(request, 'certificates/step.html', {
'instance': instance,
diff --git a/contracts/migrations/0001_initial.py b/contracts/migrations/0001_initial.py
index 8f9f4b7..25acea6 100644
--- a/contracts/migrations/0001_initial.py
+++ b/contracts/migrations/0001_initial.py
@@ -1,7 +1,6 @@
-# Generated by Django 5.2.4 on 2025-08-21 06:00
+# Generated by Django 5.2.4 on 2025-09-07 07:35
import django.db.models.deletion
-import simple_history.models
from django.conf import settings
from django.db import migrations, models
@@ -11,6 +10,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
+ ('accounts', '0001_initial'),
('processes', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
@@ -28,8 +28,7 @@ class Migration(migrations.Migration):
('slug', models.SlugField(max_length=100, unique=True, verbose_name='اسلاگ')),
('name', models.CharField(max_length=100, verbose_name='نام')),
('body', models.TextField(verbose_name='متن قرارداد')),
- ('company_logo', models.ImageField(blank=True, null=True, upload_to='contracts/logos/%Y/%m/%d/', verbose_name='لوگوی شرکت')),
- ('company_signature', models.ImageField(blank=True, null=True, upload_to='contracts/signatures/%Y/%m/%d/', verbose_name='امضای شرکت')),
+ ('company', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.company', verbose_name='شرکت')),
],
options={
'verbose_name': 'قالب قرارداد',
@@ -58,61 +57,4 @@ class Migration(migrations.Migration):
'ordering': ['-created'],
},
),
- migrations.CreateModel(
- name='HistoricalContractInstance',
- fields=[
- ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
- ('created', models.DateTimeField(blank=True, editable=False, verbose_name='تاریخ ایجاد')),
- ('updated', models.DateTimeField(blank=True, editable=False, verbose_name='تاریخ بروزرسانی')),
- ('is_active', models.BooleanField(default=True, verbose_name='فعال')),
- ('is_deleted', models.BooleanField(default=False, verbose_name='حذف شده')),
- ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')),
- ('rendered_body', models.TextField(verbose_name='متن نهایی قرارداد')),
- ('approved', models.BooleanField(default=False, verbose_name='تایید شده')),
- ('approved_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ تایید')),
- ('history_id', models.AutoField(primary_key=True, serialize=False)),
- ('history_date', models.DateTimeField(db_index=True)),
- ('history_change_reason', models.CharField(max_length=100, null=True)),
- ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
- ('created_by', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='ایجاد کننده')),
- ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
- ('process_instance', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='processes.processinstance', verbose_name='نمونه فرآیند')),
- ('template', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='contracts.contracttemplate', verbose_name='قالب مورد استفاده')),
- ],
- options={
- 'verbose_name': 'historical قرارداد',
- 'verbose_name_plural': 'historical قراردادها',
- 'ordering': ('-history_date', '-history_id'),
- 'get_latest_by': ('history_date', 'history_id'),
- },
- bases=(simple_history.models.HistoricalChanges, models.Model),
- ),
- migrations.CreateModel(
- name='HistoricalContractTemplate',
- fields=[
- ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
- ('created', models.DateTimeField(blank=True, editable=False, verbose_name='تاریخ ایجاد')),
- ('updated', models.DateTimeField(blank=True, editable=False, verbose_name='تاریخ بروزرسانی')),
- ('is_active', models.BooleanField(default=True, verbose_name='فعال')),
- ('is_deleted', models.BooleanField(default=False, verbose_name='حذف شده')),
- ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')),
- ('slug', models.SlugField(max_length=100, verbose_name='اسلاگ')),
- ('name', models.CharField(max_length=100, verbose_name='نام')),
- ('body', models.TextField(verbose_name='متن قرارداد')),
- ('company_logo', models.TextField(blank=True, max_length=100, null=True, verbose_name='لوگوی شرکت')),
- ('company_signature', models.TextField(blank=True, max_length=100, null=True, verbose_name='امضای شرکت')),
- ('history_id', models.AutoField(primary_key=True, serialize=False)),
- ('history_date', models.DateTimeField(db_index=True)),
- ('history_change_reason', models.CharField(max_length=100, null=True)),
- ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
- ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
- ],
- options={
- 'verbose_name': 'historical قالب قرارداد',
- 'verbose_name_plural': 'historical قالب\u200cهای قرارداد',
- 'ordering': ('-history_date', '-history_id'),
- 'get_latest_by': ('history_date', 'history_id'),
- },
- bases=(simple_history.models.HistoricalChanges, models.Model),
- ),
]
diff --git a/contracts/migrations/0002_remove_historicalcontracttemplate_history_user_and_more.py b/contracts/migrations/0002_remove_historicalcontracttemplate_history_user_and_more.py
deleted file mode 100644
index 60d434d..0000000
--- a/contracts/migrations/0002_remove_historicalcontracttemplate_history_user_and_more.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# Generated by Django 5.2.4 on 2025-08-21 06:33
-
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('accounts', '0002_company'),
- ('contracts', '0001_initial'),
- ]
-
- operations = [
- migrations.RemoveField(
- model_name='historicalcontracttemplate',
- name='history_user',
- ),
- migrations.RemoveField(
- model_name='contracttemplate',
- name='company_logo',
- ),
- migrations.RemoveField(
- model_name='contracttemplate',
- name='company_signature',
- ),
- migrations.AddField(
- model_name='contracttemplate',
- name='company',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.company', verbose_name='شرکت'),
- ),
- migrations.DeleteModel(
- name='HistoricalContractInstance',
- ),
- migrations.DeleteModel(
- name='HistoricalContractTemplate',
- ),
- ]
diff --git a/contracts/templates/contracts/contract_step.html b/contracts/templates/contracts/contract_step.html
index 33ff326..df4fdfc 100644
--- a/contracts/templates/contracts/contract_step.html
+++ b/contracts/templates/contracts/contract_step.html
@@ -41,32 +41,36 @@
- {% if template.company.logo %}
-
-

-
{{ contract.template.company.name }}
-
{{ contract.template.name }}
-
- {% endif %}
+ {% if can_view_contract_body %}
+ {% if template.company.logo %}
+
+

+
{{ contract.template.company.name }}
+
{{ contract.template.name }}
+
+ {% endif %}
-
تاریخ: {{ contract.jcreated }}
-
-
{{ contract.rendered_body|safe }}
-
-
-
-
-
امضای شرکت
-
- {% if template.company.signature %}
-

- {% endif %}
+
تاریخ: {{ contract.jcreated }}
+
+
{{ contract.rendered_body|safe }}
+
+
+
+
+
امضای شرکت
+
+ {% if template.company.signature %}
+

+ {% endif %}
+
-
+ {% else %}
+
شما دسترسی به مشاهده متن قرارداد را ندارید.
+ {% endif %}
diff --git a/contracts/views.py b/contracts/views.py
index f2d0deb..1949665 100644
--- a/contracts/views.py
+++ b/contracts/views.py
@@ -4,6 +4,7 @@ from django.urls import reverse
from django.utils import timezone
from django.template import Template, Context
from processes.models import ProcessInstance, StepInstance
+from common.consts import UserRoles
from .models import ContractTemplate, ContractInstance
from _helpers.utils import jalali_converter2
@@ -34,6 +35,20 @@ def contract_step(request, instance_id, step_id):
step = get_object_or_404(instance.process.steps, id=step_id)
previous_step = instance.process.steps.filter(order__lt=step.order).last()
next_step = instance.process.steps.filter(order__gt=step.order).first()
+ # Access control:
+ # - INSTALLER: can open step but cannot view contract body (show inline message)
+ # - Others: can view
+ # - Only BROKER can submit/complete this step
+ profile = getattr(request.user, 'profile', None)
+ is_broker = False
+ can_view_contract_body = True
+ try:
+ is_broker = bool(profile and profile.has_role(UserRoles.BROKER))
+ if profile and profile.has_role(UserRoles.INSTALLER):
+ can_view_contract_body = False
+ except Exception:
+ pass
+
template_obj = ContractTemplate.objects.first()
if not template_obj:
return render(request, 'contracts/contract_missing.html', {'instance': instance})
@@ -54,8 +69,11 @@ def contract_step(request, instance_id, step_id):
contract.rendered_body = rendered
contract.save()
- # If user submits to go next, mark this step completed and go to next
+ # If user submits to go next, only broker can complete and go to next
if request.method == 'POST':
+ if not is_broker:
+ from django.http import JsonResponse
+ return JsonResponse({'success': False, 'message': 'شما مجوز تایید این مرحله را ندارید'}, status=403)
StepInstance.objects.update_or_create(
process_instance=instance,
step=step,
@@ -74,6 +92,8 @@ def contract_step(request, instance_id, step_id):
'template': template_obj,
'previous_step': previous_step,
'next_step': next_step,
+ 'is_broker': is_broker,
+ 'can_view_contract_body': can_view_contract_body,
})
diff --git a/db.sqlite3 b/db.sqlite3
new file mode 100644
index 0000000..e02ec79
Binary files /dev/null and b/db.sqlite3 differ
diff --git a/installations/migrations/0001_initial.py b/installations/migrations/0001_initial.py
index 05cedcd..41f02f5 100644
--- a/installations/migrations/0001_initial.py
+++ b/installations/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.2.4 on 2025-08-21 08:25
+# Generated by Django 5.2.4 on 2025-09-07 07:35
import django.db.models.deletion
from django.conf import settings
@@ -10,7 +10,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
- ('invoices', '0002_historicalpayment_receipt_image_and_more'),
+ ('invoices', '0001_initial'),
('processes', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
@@ -53,6 +53,8 @@ class Migration(migrations.Migration):
('utm_x', models.DecimalField(blank=True, decimal_places=6, max_digits=10, null=True, verbose_name='UTM X')),
('utm_y', models.DecimalField(blank=True, decimal_places=6, max_digits=10, null=True, verbose_name='UTM Y')),
('description', models.TextField(blank=True, verbose_name='توضیحات')),
+ ('approved', models.BooleanField(default=False, verbose_name='تایید شده')),
+ ('approved_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ تایید')),
('assignment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reports', to='installations.installationassignment', verbose_name='اختصاص')),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='ایجادکننده')),
],
diff --git a/installations/migrations/0002_installationreport_approved_and_more.py b/installations/migrations/0002_installationreport_approved_and_more.py
deleted file mode 100644
index d5df3c8..0000000
--- a/installations/migrations/0002_installationreport_approved_and_more.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Generated by Django 5.2.4 on 2025-08-21 09:04
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('installations', '0001_initial'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='installationreport',
- name='approved',
- field=models.BooleanField(default=False, verbose_name='تایید شده'),
- ),
- migrations.AddField(
- model_name='installationreport',
- name='approved_at',
- field=models.DateTimeField(blank=True, null=True, verbose_name='تاریخ تایید'),
- ),
- ]
diff --git a/installations/templates/installations/installation_assign_step.html b/installations/templates/installations/installation_assign_step.html
index c1d6c79..2bdbe4d 100644
--- a/installations/templates/installations/installation_assign_step.html
+++ b/installations/templates/installations/installation_assign_step.html
@@ -1,6 +1,7 @@
{% extends '_base.html' %}
{% load static %}
{% load processes_tags %}
+{% load common_tags %}
{% load humanize %}
{% block sidebar %}
@@ -41,12 +42,15 @@
+ {% if show_denied_msg %}
+
شما اجازه تعیین نصاب را ندارید.
+ {% endif %}
diff --git a/installations/templates/installations/installation_report_step.html b/installations/templates/installations/installation_report_step.html
index 275f7bc..6044f53 100644
--- a/installations/templates/installations/installation_report_step.html
+++ b/installations/templates/installations/installation_report_step.html
@@ -2,6 +2,7 @@
{% load static %}
{% load processes_tags %}
{% load common_tags %}
+{% load accounts_tags %}
{% load humanize %}
{% block sidebar %}
@@ -19,6 +20,18 @@
+
+
{% endblock %}
{% block content %}
@@ -41,13 +54,27 @@
{% stepper_header instance step %}
-
{% if report and not edit_mode %}
-