diff --git a/core/apps/contracts/admins/contract.py b/core/apps/contracts/admins/contract.py new file mode 100644 index 0000000..c14c16e --- /dev/null +++ b/core/apps/contracts/admins/contract.py @@ -0,0 +1,24 @@ +from django.contrib import admin + +from core.apps.contracts.models.contract import Contract, ContractFile, ContractNotification,ContractSide, ContractSignature + + +@admin.register(Contract) +class ContractAdmin(admin.ModelAdmin): + list_display = ['id', 'contract_number', 'name', 'face_id', 'attach_file', 'add_folder', 'add_notification'] + +@admin.register(ContractSide) +class ContractSideAdmin(admin.ModelAdmin): + list_display = ['id', 'full_name'] + +@admin.register(ContractFile) +class ContractFileAdmin(admin.ModelAdmin): + list_display = ['id', 'name'] + +@admin.register(ContractNotification) +class ContractNotificationAdmin(admin.ModelAdmin): + list_display = ['id', 'message', 'contract'] + +@admin.register(ContractSignature) +class ContractSignatureAdmin(admin.ModelAdmin): + list_display = ['id', 'user', 'contract', 'status'] \ No newline at end of file diff --git a/core/apps/contracts/apps.py b/core/apps/contracts/apps.py index 93d8d92..3a9bb95 100644 --- a/core/apps/contracts/apps.py +++ b/core/apps/contracts/apps.py @@ -4,3 +4,6 @@ from django.apps import AppConfig class ContractsConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'core.apps.contracts' + + def ready(self): + import core.apps.contracts.admins.contract \ No newline at end of file diff --git a/core/apps/contracts/enums/__init__.py b/core/apps/contracts/enums/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/contracts/enums/contract.py b/core/apps/contracts/enums/contract.py new file mode 100644 index 0000000..8001e35 --- /dev/null +++ b/core/apps/contracts/enums/contract.py @@ -0,0 +1,12 @@ +SIDES = ( + ('two_or_more', 'two or more',), + ('customer_only', 'customer only',), + ('only_company', 'onlycompany'), +) + +STATUS = ( + ('created', 'created',), + ('signed_company', 'signed by company',), + ('signed_customer', 'signed by customer',), + ('cancelled', 'cancelled') +) \ No newline at end of file diff --git a/core/apps/contracts/enums/contract_file.py b/core/apps/contracts/enums/contract_file.py new file mode 100644 index 0000000..3a700db --- /dev/null +++ b/core/apps/contracts/enums/contract_file.py @@ -0,0 +1,12 @@ +FILE_TYPE = ( + ('all_files', 'all files',), + ('PDF', 'pdf',), + ('word_files', 'word files',), + ('image', 'image'), +) + +USER_PERMISSION = ( + ('all', 'all'), + ('add', 'add'), + ('delete', 'delete'), +) \ No newline at end of file diff --git a/core/apps/contracts/enums/contract_notification.py b/core/apps/contracts/enums/contract_notification.py new file mode 100644 index 0000000..49f0084 --- /dev/null +++ b/core/apps/contracts/enums/contract_notification.py @@ -0,0 +1,15 @@ +NOTIFICATION_EVENT = ( + ('exact_date', 'exact date'), + ('counterparty_signing', 'counterparty signing'), + ('signature_all_parties', 'Signature of all parties') +) + +TYPE_NOTIFICATION = ( + ('sms', 'sms'), + ('whatsup', 'whatsup'), +) + +TYPE_MESSAGE = ( + ('krill', 'krill'), + ('latin', 'latin'), +) \ No newline at end of file diff --git a/core/apps/contracts/enums/contract_side.py b/core/apps/contracts/enums/contract_side.py new file mode 100644 index 0000000..dca1d9e --- /dev/null +++ b/core/apps/contracts/enums/contract_side.py @@ -0,0 +1,4 @@ +ROLE = ( + ('physical', 'physical'), + ('legal', 'legal'), +) \ No newline at end of file diff --git a/core/apps/contracts/enums/contract_signature.py b/core/apps/contracts/enums/contract_signature.py new file mode 100644 index 0000000..cfb136a --- /dev/null +++ b/core/apps/contracts/enums/contract_signature.py @@ -0,0 +1,9 @@ +SIGNATURE_STATUS = ( + ('signed', 'signed'), + ('organized', 'organized') +) + +SIGNATURE_TYPE = ( + ('sms_sign', 'sms signature'), + ('electronic_sing', 'electronic signature') +) \ No newline at end of file diff --git a/core/apps/contracts/migrations/0001_initial.py b/core/apps/contracts/migrations/0001_initial.py new file mode 100644 index 0000000..c9b716c --- /dev/null +++ b/core/apps/contracts/migrations/0001_initial.py @@ -0,0 +1,116 @@ +# Generated by Django 5.2 on 2025-07-15 15:55 + +import django.contrib.postgres.fields +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Contract', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('file', models.FileField(upload_to='contract/files/%Y/%m/')), + ('contract_number', models.PositiveIntegerField()), + ('name', models.CharField(max_length=200)), + ('sides', models.CharField(choices=[('two_or_more', 'two or more'), ('customer_only', 'customer only'), ('only_company', 'onlycompany')], max_length=13)), + ('status', models.CharField(choices=[('created', 'created'), ('signed_company', 'signed by company'), ('signed_customer', 'signed by customer'), ('cancelled', 'cancelled')], max_length=15)), + ('face_id', models.BooleanField(default=False)), + ('attach_file', models.BooleanField(default=False)), + ('add_folder', models.BooleanField(default=False)), + ('add_notification', models.BooleanField(default=False)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contracts', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'contract', + 'verbose_name_plural': 'contracts', + 'db_table': 'contracts', + }, + ), + migrations.CreateModel( + name='ContractFile', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=150)), + ('file_type', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('all_files', 'all files'), ('PDF', 'pdf'), ('word_files', 'word files'), ('image', 'image')], max_length=10), blank=True, default=list, size=None)), + ('user_permission', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('all', 'all'), ('add', 'add'), ('delete', 'delete')], max_length=10), blank=True, default=list, size=None)), + ('contract', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contract_files', to='contracts.contract')), + ], + options={ + 'verbose_name': 'contract file', + 'verbose_name_plural': 'contract files', + 'db_table': 'contract_files', + }, + ), + migrations.CreateModel( + name='ContractNotification', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('notification_event', models.CharField(choices=[('exact_date', 'exact date'), ('counterparty_signing', 'counterparty signing'), ('signature_all_parties', 'Signature of all parties')], max_length=24)), + ('type_notification', models.CharField(choices=[('sms', 'sms'), ('whatsup', 'whatsup')], max_length=8)), + ('type_message', models.CharField(blank=True, choices=[('krill', 'krill'), ('latin', 'latin')], max_length=8, null=True)), + ('message', models.CharField(max_length=160)), + ('time', models.TimeField(blank=True, null=True)), + ('date', models.DateField(blank=True, null=True)), + ('contract', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contract_notifications', to='contracts.contract')), + ], + options={ + 'verbose_name': 'contract notification', + 'verbose_name_plural': 'contract notifications', + 'db_table': 'contract_notifications', + }, + ), + migrations.CreateModel( + name='ContractSide', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('full_name', models.CharField(max_length=70)), + ('indentification', models.CharField(max_length=20)), + ('position', models.CharField(blank=True, max_length=50, null=True)), + ('has_indentification', models.BooleanField(default=False)), + ('user_role', models.CharField(choices=[('physical', 'physical'), ('legal', 'legal')], max_length=9)), + ('contract', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contract_sides', to='contracts.contract')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contract_sides', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'contract side', + 'verbose_name_plural': 'contract sides', + 'db_table': 'contracts_sides', + }, + ), + migrations.CreateModel( + name='ContractSignature', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('status', models.CharField(choices=[('signed', 'signed'), ('organized', 'organized')], max_length=20)), + ('signature_type', models.CharField(choices=[('sms_sign', 'sms signature'), ('electronic_sing', 'electronic signature')], max_length=20)), + ('is_signature', models.BooleanField(default=False)), + ('contract', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contract_signatures', to='contracts.contract')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contract_users', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'contract signatures', + 'db_table': 'contract_signatures', + }, + ), + ] diff --git a/core/apps/contracts/models/__init__.py b/core/apps/contracts/models/__init__.py index e69de29..d3aea48 100644 --- a/core/apps/contracts/models/__init__.py +++ b/core/apps/contracts/models/__init__.py @@ -0,0 +1 @@ +from .contract import * \ No newline at end of file diff --git a/core/apps/contracts/models/contract.py b/core/apps/contracts/models/contract.py new file mode 100644 index 0000000..6aee237 --- /dev/null +++ b/core/apps/contracts/models/contract.py @@ -0,0 +1,117 @@ +from django.db import models +from django.contrib.auth import get_user_model +from django.contrib.postgres.fields import ArrayField + +from core.apps.shared.models.base import BaseModel +from core.apps.contracts.enums.contract import SIDES, STATUS +from core.apps.contracts.enums.contract_side import ROLE +from core.apps.contracts.enums.contract_file import FILE_TYPE, USER_PERMISSION +from core.apps.contracts.enums.contract_signature import SIGNATURE_TYPE, SIGNATURE_STATUS +from core.apps.contracts.enums.contract_notification import NOTIFICATION_EVENT, TYPE_MESSAGE, TYPE_NOTIFICATION +from core.apps.accounts.validators.user import phone_regex + + +class Contract(BaseModel): + file = models.FileField(upload_to="contract/files/%Y/%m/") + contract_number = models.PositiveIntegerField() + name = models.CharField(max_length=200) + + sides = models.CharField(max_length=13, choices=SIDES) # choices + status = models.CharField(max_length=15, choices=STATUS) # choices + + face_id = models.BooleanField(default=False) + attach_file = models.BooleanField(default=False) + add_folder = models.BooleanField(default=False) + add_notification = models.BooleanField(default=False) + + user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='contracts') + + def __str__(self): + return f'{self.name} - {self.user}' + + class Meta: + verbose_name = 'contract' + verbose_name_plural = 'contracts' + db_table = 'contracts' + + +class ContractSide(BaseModel): + full_name = models.CharField(max_length=70) + indentification = models.CharField(max_length=20) + position = models.CharField(max_length=50, null=True, blank=True) + + has_indentification = models.BooleanField(default=False) + + user_role = models.CharField(max_length=9, choices=ROLE) + + contract = models.ForeignKey(Contract, on_delete=models.CASCADE, related_name='contract_sides') + user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='contract_sides') + + def __str__(self): + return f"{self.full_name} - {self.contract}" + + class Meta: + verbose_name = 'contract side' + verbose_name_plural = 'contract sides' + db_table = 'contracts_sides' + + +class ContractFile(BaseModel): + name = models.CharField(max_length=150) + file_type = ArrayField( + models.CharField(max_length=10, choices=FILE_TYPE), + default=list, + blank=True + ) + user_permission = ArrayField( + models.CharField(max_length=10, choices=USER_PERMISSION), + default=list, + blank=True + ) + contract = models.ForeignKey(Contract, on_delete=models.CASCADE, related_name='contract_files') + + def __str__(self): + return f'{self.name} - {self.contract}' + + class Meta: + verbose_name = 'contract file' + verbose_name_plural = 'contract files' + db_table = 'contract_files' + + +class ContractNotification(BaseModel): + notification_event = models.CharField(choices=NOTIFICATION_EVENT, max_length=24) + type_notification = models.CharField(choices=TYPE_NOTIFICATION, max_length=8) + type_message = models.CharField(null=True, blank=True, choices=TYPE_MESSAGE, max_length=8) + + message = models.CharField(max_length=160) + time = models.TimeField(null=True, blank=True) + date = models.DateField(null=True, blank=True) + + contract = models.ForeignKey(Contract, on_delete=models.CASCADE, related_name="contract_notifications") + + def __str__(self): + return f'{self.type_notification} - {self.contract}' + + class Meta: + verbose_name = 'contract notification' + verbose_name_plural = 'contract notifications' + db_table = 'contract_notifications' + + +class ContractSignature(BaseModel): + contract = models.ForeignKey(Contract, on_delete=models.CASCADE, related_name='contract_signatures') + user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='contract_users') + + status = models.CharField(max_length=20, choices=SIGNATURE_STATUS) # choices + signature_type = models.CharField(max_length=20, choices=SIGNATURE_TYPE) # choices + + is_signature = models.BooleanField(default=False) + + def __str__(self): + return f'{self.user} user signature for {self.contract} contract' + + class Meta: + verbose_name = 'contract signature' + verbose_name = 'contract signatures' + db_table = 'contract_signatures' diff --git a/core/apps/shared/admins/folder.py b/core/apps/shared/admins/folder.py new file mode 100644 index 0000000..fdc63b3 --- /dev/null +++ b/core/apps/shared/admins/folder.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +from core.apps.shared.models.folder import Folder + + +@admin.register(Folder) +class FolderAdmin(admin.ModelAdmin): + list_display = ['id', 'name'] \ No newline at end of file diff --git a/core/apps/shared/apps.py b/core/apps/shared/apps.py index ca9e94d..1ed748d 100644 --- a/core/apps/shared/apps.py +++ b/core/apps/shared/apps.py @@ -4,3 +4,6 @@ from django.apps import AppConfig class SharedConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'core.apps.shared' + + def ready(self): + import core.apps.shared.admins.folder \ No newline at end of file diff --git a/core/apps/shared/migrations/0001_initial.py b/core/apps/shared/migrations/0001_initial.py new file mode 100644 index 0000000..f66a57e --- /dev/null +++ b/core/apps/shared/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2 on 2025-07-15 15:36 + +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Folder', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=200)), + ], + options={ + 'verbose_name': 'folder', + 'verbose_name_plural': 'folders', + 'db_table': 'folders', + }, + ), + ] diff --git a/core/apps/shared/models/__init__.py b/core/apps/shared/models/__init__.py index 773cfc4..59c3224 100644 --- a/core/apps/shared/models/__init__.py +++ b/core/apps/shared/models/__init__.py @@ -1 +1,2 @@ -from .base import * \ No newline at end of file +from .base import * +from .folder import * \ No newline at end of file diff --git a/core/apps/shared/models/folder.py b/core/apps/shared/models/folder.py new file mode 100644 index 0000000..28ed42d --- /dev/null +++ b/core/apps/shared/models/folder.py @@ -0,0 +1,15 @@ +from django.db import models + +from core.apps.shared.models.base import BaseModel + + +class Folder(BaseModel): + name = models.CharField(max_length=200) + + def __str__(self): + return self.name + + class Meta: + verbose_name = 'folder' + verbose_name_plural = 'folders' + db_table = 'folders'