complete first version of main proccess
This commit is contained in:
		
							parent
							
								
									6ff4740d04
								
							
						
					
					
						commit
						f2fc2362a7
					
				
					 61 changed files with 3280 additions and 28 deletions
				
			
		
							
								
								
									
										0
									
								
								contracts/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								contracts/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										15
									
								
								contracts/admin.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								contracts/admin.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
from django.contrib import admin
 | 
			
		||||
from .models import ContractTemplate, ContractInstance
 | 
			
		||||
 | 
			
		||||
@admin.register(ContractTemplate)
 | 
			
		||||
class ContractTemplateAdmin(admin.ModelAdmin):
 | 
			
		||||
    list_display = ['name',]
 | 
			
		||||
    search_fields = ['name']
 | 
			
		||||
    prepopulated_fields = {'slug': ('name',)}
 | 
			
		||||
    readonly_fields = ['created', 'updated']
 | 
			
		||||
 | 
			
		||||
@admin.register(ContractInstance)
 | 
			
		||||
class ContractInstanceAdmin(admin.ModelAdmin):
 | 
			
		||||
    list_display = ['process_instance', 'template']
 | 
			
		||||
    search_fields = ['process_instance__code', 'template__name']
 | 
			
		||||
    readonly_fields = ['created', 'updated']
 | 
			
		||||
							
								
								
									
										8
									
								
								contracts/apps.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								contracts/apps.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
from django.apps import AppConfig
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ContractsConfig(AppConfig):
 | 
			
		||||
    default_auto_field = 'django.db.models.BigAutoField'
 | 
			
		||||
    name = 'contracts'
 | 
			
		||||
    verbose_name = 'قراردادها'
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										118
									
								
								contracts/migrations/0001_initial.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								contracts/migrations/0001_initial.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,118 @@
 | 
			
		|||
# Generated by Django 5.2.4 on 2025-08-21 06:00
 | 
			
		||||
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
import simple_history.models
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    initial = True
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('processes', '0001_initial'),
 | 
			
		||||
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='ContractTemplate',
 | 
			
		||||
            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='نام')),
 | 
			
		||||
                ('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='امضای شرکت')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'verbose_name': 'قالب قرارداد',
 | 
			
		||||
                'verbose_name_plural': 'قالب\u200cهای قرارداد',
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='ContractInstance',
 | 
			
		||||
            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='تاریخ حذف')),
 | 
			
		||||
                ('rendered_body', models.TextField(verbose_name='متن نهایی قرارداد')),
 | 
			
		||||
                ('approved', models.BooleanField(default=False, verbose_name='تایید شده')),
 | 
			
		||||
                ('approved_at', models.DateTimeField(blank=True, null=True, 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='ایجاد کننده')),
 | 
			
		||||
                ('process_instance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contracts', to='processes.processinstance', verbose_name='نمونه فرآیند')),
 | 
			
		||||
                ('template', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contracts.contracttemplate', verbose_name='قالب مورد استفاده')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'verbose_name': 'قرارداد',
 | 
			
		||||
                'verbose_name_plural': 'قراردادها',
 | 
			
		||||
                '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),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
# 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',
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
							
								
								
									
										0
									
								
								contracts/migrations/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								contracts/migrations/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										36
									
								
								contracts/models.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								contracts/models.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
from django.db import models
 | 
			
		||||
from django.contrib.auth import get_user_model
 | 
			
		||||
from common.models import NameSlugModel, BaseModel
 | 
			
		||||
from accounts.models import Company
 | 
			
		||||
 | 
			
		||||
User = get_user_model()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ContractTemplate(NameSlugModel):
 | 
			
		||||
    body = models.TextField(verbose_name='متن قرارداد')
 | 
			
		||||
    company = models.ForeignKey(Company, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='شرکت')
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = 'قالب قرارداد'
 | 
			
		||||
        verbose_name_plural = 'قالبهای قرارداد'
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ContractInstance(BaseModel):
 | 
			
		||||
    process_instance = models.ForeignKey('processes.ProcessInstance', on_delete=models.CASCADE, related_name='contracts', verbose_name='نمونه فرآیند')
 | 
			
		||||
    template = models.ForeignKey(ContractTemplate, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='قالب مورد استفاده')
 | 
			
		||||
    rendered_body = models.TextField(verbose_name='متن نهایی قرارداد')
 | 
			
		||||
    approved = models.BooleanField(default=False, verbose_name='تایید شده')
 | 
			
		||||
    approved_at = models.DateTimeField(null=True, blank=True, verbose_name='تاریخ تایید')
 | 
			
		||||
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='ایجاد کننده')
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = 'قرارداد'
 | 
			
		||||
        verbose_name_plural = 'قراردادها'
 | 
			
		||||
        ordering = ['-created']
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"Contract for {self.process_instance}"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										29
									
								
								contracts/templates/contracts/contract_missing.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								contracts/templates/contracts/contract_missing.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
{% extends '_base.html' %}
 | 
			
		||||
{% load static %}
 | 
			
		||||
 | 
			
		||||
{% block sidebar %}
 | 
			
		||||
  {% include 'sidebars/admin.html' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block navbar %}
 | 
			
		||||
  {% include 'navbars/admin.html' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block title %}قالب قرارداد یافت نشد{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container-xxl flex-grow-1 container-p-y">
 | 
			
		||||
  <div class="alert alert-warning">
 | 
			
		||||
    <h5 class="alert-heading mb-2">قالب قرارداد تعریف نشده است</h5>
 | 
			
		||||
    <p class="mb-2">برای نمایش این مرحله، ابتدا یک «قالب قرارداد» ایجاد کنید.</p>
 | 
			
		||||
    <ul class="mb-2">
 | 
			
		||||
      <li>از منوی ادمین یک قالب با متن قرارداد ایجاد کنید.</li>
 | 
			
		||||
      <li>در متن میتوانید از جاینگهدارهایی مثل {{customer_full_name}} ، {{national_code}} ، {{water_subscription_number}} استفاده کنید.</li>
 | 
			
		||||
    </ul>
 | 
			
		||||
    <a class="btn btn-primary" href="/admin/contracts/contracttemplate/add/">ایجاد قالب قرارداد</a>
 | 
			
		||||
    <a class="btn btn-outline-secondary" href="{% url 'processes:request_list' %}">بازگشت</a>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										52
									
								
								contracts/templates/contracts/contract_print.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								contracts/templates/contracts/contract_print.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,52 @@
 | 
			
		|||
<!DOCTYPE html>
 | 
			
		||||
<html lang="fa" dir="rtl">
 | 
			
		||||
<head>
 | 
			
		||||
  <meta charset="utf-8">
 | 
			
		||||
  <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
			
		||||
  <title>چاپ قرارداد {{ instance.code }}</title>
 | 
			
		||||
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
 | 
			
		||||
  <style>
 | 
			
		||||
    @page { size: A4; margin: 1.2cm; }
 | 
			
		||||
    body { font-family: 'Vazirmatn', sans-serif; }
 | 
			
		||||
    .logo { max-height: 80px; }
 | 
			
		||||
    .signature { height: 90px; border: 1px dashed #ccc; }
 | 
			
		||||
  </style>
 | 
			
		||||
  <script>
 | 
			
		||||
    window.addEventListener('load', function(){ setTimeout(function(){ window.print(); }, 300); });
 | 
			
		||||
  </script>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
  <div class="container-fluid">
 | 
			
		||||
    <div class="d-flex justify-content-between align-items-center mb-3">
 | 
			
		||||
      <div>
 | 
			
		||||
        <h5>{{ contract.template.company.name }}</h5>
 | 
			
		||||
        <h5 class="mb-1">{{ contract.template.name }}</h5>
 | 
			
		||||
        <div class="text-muted small">کد درخواست: {{ instance.code }} | تاریخ: {{ contract.jcreated }}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
      {% if contract.template.company.logo %}
 | 
			
		||||
        <img class="logo" src="{{ contract.template.company.logo.url }}" alt="لوگو" />
 | 
			
		||||
      {% endif %}
 | 
			
		||||
      
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr>
 | 
			
		||||
    <div style="white-space: pre-line; line-height: 1.9;">{{ contract.rendered_body|safe }}</div>
 | 
			
		||||
    <hr>
 | 
			
		||||
    <div class="row mt-4">
 | 
			
		||||
      <div class="col-6 text-center">
 | 
			
		||||
        <div>امضای مشترک</div>
 | 
			
		||||
        <div class="signature mt-2"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="col-6 text-center">
 | 
			
		||||
        <div>امضای شرکت</div>
 | 
			
		||||
        <div class="signature mt-2">
 | 
			
		||||
          {% if contract.template.company.signature %}
 | 
			
		||||
            <img src="{{ contract.template.company.signature.url }}" alt="امضای شرکت" style="max-height: 80px;" />
 | 
			
		||||
          {% endif %}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										92
									
								
								contracts/templates/contracts/contract_step.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								contracts/templates/contracts/contract_step.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,92 @@
 | 
			
		|||
{% extends '_base.html' %}
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load processes_tags %}
 | 
			
		||||
{% load humanize %}
 | 
			
		||||
 | 
			
		||||
{% block sidebar %}
 | 
			
		||||
    {% include 'sidebars/admin.html' %}
 | 
			
		||||
{% endblock sidebar %}
 | 
			
		||||
 | 
			
		||||
{% block navbar %}
 | 
			
		||||
    {% include 'navbars/admin.html' %}
 | 
			
		||||
{% endblock navbar %}
 | 
			
		||||
 | 
			
		||||
{% block title %}{{ step.name }} - درخواست {{ instance.code }}{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block style %}
 | 
			
		||||
<link rel="stylesheet" href="{% static 'assets/vendor/libs/bs-stepper/bs-stepper.css' %}">
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
{% include '_toasts.html' %}
 | 
			
		||||
<div class="container-xxl flex-grow-1 container-p-y">
 | 
			
		||||
  <div class="row">
 | 
			
		||||
    <div class="col-12 mb-4">
 | 
			
		||||
      <div class="d-flex align-items-center justify-content-between mb-3">
 | 
			
		||||
        <div>
 | 
			
		||||
          <h4 class="mb-1">{{ step.name }}: {{ instance.process.name }}</h4>
 | 
			
		||||
          <small class="text-muted d-block">
 | 
			
		||||
            اشتراک آب: {{ instance.well.water_subscription_number|default:"-" }}
 | 
			
		||||
            | نماینده: {{ instance.representative.profile.national_code|default:"-" }}
 | 
			
		||||
          </small>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="d-flex gap-2">
 | 
			
		||||
          <a href="{% url 'processes:request_list' %}" class="btn btn-outline-secondary">بازگشت</a>
 | 
			
		||||
          <a href="{% url 'contracts:contract_print' instance.id %}" target="_blank" class="btn btn-outline-secondary">پرینت</a>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="bs-stepper wizard-vertical vertical mt-2">
 | 
			
		||||
        {% stepper_header instance step %}
 | 
			
		||||
        <div class="bs-stepper-content">
 | 
			
		||||
          <div class="card border">
 | 
			
		||||
            <div class="card-body">
 | 
			
		||||
              {% if template.company.logo %}
 | 
			
		||||
                <div class="text-center mb-3">
 | 
			
		||||
                  <img src="{{ template.company.logo.url }}" alt="لوگوی شرکت" style="max-height:80px;">
 | 
			
		||||
                  <h4 class="text-muted">{{ contract.template.company.name }}</h4>
 | 
			
		||||
                  <h5 class="text-muted">{{ contract.template.name }}</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
              {% endif %}
 | 
			
		||||
 | 
			
		||||
              <div class="small text-muted mb-2">تاریخ: {{ contract.jcreated }}</div>
 | 
			
		||||
              <hr>
 | 
			
		||||
              <div class="contract-body" style="white-space: pre-line; line-height:1.9;">{{ contract.rendered_body|safe }}</div>
 | 
			
		||||
              <hr>
 | 
			
		||||
              <div class="row mt-4">
 | 
			
		||||
                <div class="col-6 text-center">
 | 
			
		||||
                  <div>امضای مشترک</div>
 | 
			
		||||
                  <div style="height:90px;border:1px dashed #ccc; margin-top:10px;"></div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="col-6 text-center">
 | 
			
		||||
                  <div>امضای شرکت</div>
 | 
			
		||||
                  <div style="height:90px;border:1px dashed #ccc; margin-top:10px;">
 | 
			
		||||
                    {% if template.company.signature %}
 | 
			
		||||
                      <img src="{{ template.company.signature.url }}" alt="امضای شرکت" style="max-height:80px;">
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <form method="post" class="d-flex justify-content-between mt-3">
 | 
			
		||||
            {% csrf_token %}
 | 
			
		||||
            {% if previous_step %}
 | 
			
		||||
              <a href="{% url 'processes:step_detail' instance.id previous_step.id %}" class="btn btn-label-secondary">قبلی</a>
 | 
			
		||||
            {% else %}
 | 
			
		||||
              <span></span>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
            {% if next_step %}
 | 
			
		||||
              <button type="submit" class="btn btn-primary">بعدی</button>
 | 
			
		||||
            {% else %}
 | 
			
		||||
              <button class="btn btn-success" type="button">اتمام</button>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
          </form>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										11
									
								
								contracts/urls.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								contracts/urls.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
from django.urls import path
 | 
			
		||||
from . import views
 | 
			
		||||
 | 
			
		||||
app_name = 'contracts'
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path('instance/<int:instance_id>/step/<int:step_id>/', views.contract_step, name='contract_step'),
 | 
			
		||||
    path('instance/<int:instance_id>/print/', views.contract_print, name='contract_print'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										89
									
								
								contracts/views.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								contracts/views.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,89 @@
 | 
			
		|||
from django.shortcuts import render, get_object_or_404, redirect
 | 
			
		||||
from django.contrib.auth.decorators import login_required
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.template import Template, Context
 | 
			
		||||
from processes.models import ProcessInstance, StepInstance
 | 
			
		||||
from .models import ContractTemplate, ContractInstance
 | 
			
		||||
from _helpers.utils import jalali_converter2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def build_contract_context(instance: ProcessInstance) -> dict:
 | 
			
		||||
    representative = instance.representative
 | 
			
		||||
    profile = getattr(representative, 'profile', None)
 | 
			
		||||
    well = instance.well
 | 
			
		||||
    return {
 | 
			
		||||
        'customer_full_name': representative.get_full_name() if representative else '',
 | 
			
		||||
        'national_code': profile.national_code if profile else '',
 | 
			
		||||
        'address': profile.address if profile else '',
 | 
			
		||||
        'phone': profile.phone_number_1 if profile else '',
 | 
			
		||||
        'phone2': profile.phone_number_2 if profile else '',
 | 
			
		||||
        'water_subscription_number': well.water_subscription_number if well else '',
 | 
			
		||||
        'electricity_subscription_number': well.electricity_subscription_number if well else '',
 | 
			
		||||
        'water_meter_serial_number': well.water_meter_serial_number if well else '',
 | 
			
		||||
        'well_power': well.well_power if well else '',
 | 
			
		||||
        'request_code': instance.code,
 | 
			
		||||
        'today': jalali_converter2(timezone.now()),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def contract_step(request, instance_id, step_id):
 | 
			
		||||
    instance = get_object_or_404(ProcessInstance, id=instance_id)
 | 
			
		||||
    # Resolve step navigation
 | 
			
		||||
    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()
 | 
			
		||||
    template_obj = ContractTemplate.objects.first()
 | 
			
		||||
    if not template_obj:
 | 
			
		||||
        return render(request, 'contracts/contract_missing.html', {'instance': instance})
 | 
			
		||||
 | 
			
		||||
    ctx = build_contract_context(instance)
 | 
			
		||||
    rendered = Template(template_obj.body).render(Context(ctx))
 | 
			
		||||
 | 
			
		||||
    contract, _ = ContractInstance.objects.get_or_create(
 | 
			
		||||
        process_instance=instance,
 | 
			
		||||
        defaults={
 | 
			
		||||
            'template': template_obj,
 | 
			
		||||
            'rendered_body': rendered,
 | 
			
		||||
            'created_by': request.user,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    # keep latest rendering if template changed (optional)
 | 
			
		||||
    contract.template = template_obj
 | 
			
		||||
    contract.rendered_body = rendered
 | 
			
		||||
    contract.save()
 | 
			
		||||
 | 
			
		||||
    # If user submits to go next, mark this step completed and go to next
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        StepInstance.objects.update_or_create(
 | 
			
		||||
            process_instance=instance,
 | 
			
		||||
            step=step,
 | 
			
		||||
            defaults={'status': 'completed', 'completed_at': timezone.now()}
 | 
			
		||||
        )
 | 
			
		||||
        if next_step:
 | 
			
		||||
            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')
 | 
			
		||||
 | 
			
		||||
    return render(request, 'contracts/contract_step.html', {
 | 
			
		||||
        'instance': instance,
 | 
			
		||||
        'step': step,
 | 
			
		||||
        'contract': contract,
 | 
			
		||||
        'template': template_obj,
 | 
			
		||||
        'previous_step': previous_step,
 | 
			
		||||
        'next_step': next_step,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def contract_print(request, instance_id):
 | 
			
		||||
    instance = get_object_or_404(ProcessInstance, id=instance_id)
 | 
			
		||||
    contract = get_object_or_404(ContractInstance, process_instance=instance)
 | 
			
		||||
    return render(request, 'contracts/contract_print.html', {
 | 
			
		||||
        'instance': instance,
 | 
			
		||||
        'contract': contract,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue