contract signature part is done
This commit is contained in:
@@ -26,7 +26,7 @@ class User(BaseModel, AbstractUser):
|
||||
return self.phone
|
||||
|
||||
def generate_code(self):
|
||||
code = ''.join([str(random.randint(0, 100) % 10) for _ in range(4)])
|
||||
code = ''.join([str(random.randint(1, 100) % 10) for _ in range(4)])
|
||||
expiration_time = timezone.now() + datetime.timedelta(minutes=2)
|
||||
VerificationCode.objects.create(
|
||||
code=code,
|
||||
|
||||
@@ -3,9 +3,11 @@ from celery import shared_task
|
||||
from core.apps.accounts.models.verification_code import VerificationCode
|
||||
from core.apps.accounts.models.user import User
|
||||
from core.services.sms import send_sms_eskiz
|
||||
from core.services.sms_via_bot import send_sms_code
|
||||
|
||||
@shared_task
|
||||
def create_and_send_sms_code(user):
|
||||
user = User.objects.get(id=user)
|
||||
code = user.generate_code()
|
||||
send_sms_eskiz(user.phone, code)
|
||||
# send_sms_eskiz(user.phone, code)
|
||||
send_sms_code(code, 'auth', user.phone)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from core.apps.contracts.models.contract import Contract, ContractSide, ContractSignature
|
||||
from core.apps.contracts.models.contract import Contract, ContractSide, ContractSignature, ContractSignatureCode
|
||||
|
||||
|
||||
@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']
|
||||
@@ -14,4 +15,9 @@ class ContractSideAdmin(admin.ModelAdmin):
|
||||
|
||||
@admin.register(ContractSignature)
|
||||
class ContractSignatureAdmin(admin.ModelAdmin):
|
||||
list_display = ['id', 'user', 'contract', 'status']
|
||||
list_display = ['id', 'contract_side', 'contract', 'status']
|
||||
|
||||
|
||||
@admin.register(ContractSignatureCode)
|
||||
class ContractSignatureCodeAdmin(admin.ModelAdmin):
|
||||
list_display = ['id', 'code', 'signature']
|
||||
@@ -8,5 +8,6 @@ STATUS = (
|
||||
('created', 'created',),
|
||||
('signed_company', 'signed by company',),
|
||||
('signed_customer', 'signed by customer',),
|
||||
('cancelled', 'cancelled')
|
||||
('contract_signed', 'contract signed'),
|
||||
('cancelled', 'cancelled'),
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2 on 2025-07-16 17:14
|
||||
# Generated by Django 5.2 on 2025-07-17 14:48
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
@@ -25,7 +25,7 @@ class Migration(migrations.Migration):
|
||||
('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')], default='created', max_length=15)),
|
||||
('status', models.CharField(choices=[('created', 'created'), ('signed_company', 'signed by company'), ('signed_customer', 'signed by customer'), ('contract_signed', 'contract signed'), ('cancelled', 'cancelled')], default='created', max_length=15)),
|
||||
('face_id', models.BooleanField(default=False)),
|
||||
('attach_file', models.BooleanField(default=False)),
|
||||
('add_folder', models.BooleanField(default=False)),
|
||||
@@ -69,12 +69,29 @@ class Migration(migrations.Migration):
|
||||
('signature_type', models.CharField(blank=True, choices=[('sms_sign', 'sms signature'), ('electronic_sing', 'electronic signature')], max_length=20, null=True)),
|
||||
('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)),
|
||||
('contract_side', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='contract_signatures', to='contracts.contractside')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'contract signatures',
|
||||
'verbose_name': 'contract signature',
|
||||
'verbose_name_plural': 'contract signatures',
|
||||
'db_table': 'contract_signatures',
|
||||
'unique_together': {('contract', 'user')},
|
||||
'unique_together': {('contract', 'contract_side')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ContractSignatureCode',
|
||||
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)),
|
||||
('code', models.PositiveSmallIntegerField()),
|
||||
('expiration_time', models.DateTimeField()),
|
||||
('signature', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='signature_codes', to='contracts.contractsignature')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'contract signature code',
|
||||
'verbose_name_plural': 'contract signature codes',
|
||||
'db_table': 'contract_signature_codes',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import random
|
||||
from datetime import timedelta
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.utils import timezone
|
||||
|
||||
from core.apps.shared.models.base import BaseModel
|
||||
from core.apps.contracts.enums.contract import SIDES, STATUS
|
||||
@@ -56,7 +59,7 @@ class ContractSide(BaseModel):
|
||||
|
||||
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')
|
||||
contract_side = models.OneToOneField(ContractSide, on_delete=models.CASCADE, related_name='contract_signatures')
|
||||
|
||||
status = models.CharField(max_length=20, choices=SIGNATURE_STATUS, default='organized')
|
||||
signature_type = models.CharField(max_length=20, choices=SIGNATURE_TYPE, null=True, blank=True)
|
||||
@@ -64,10 +67,34 @@ class ContractSignature(BaseModel):
|
||||
is_signature = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.user} user signature for {self.contract} contract'
|
||||
return f'{self.contract_side} user signature for {self.contract} contract'
|
||||
|
||||
def generate_code(self):
|
||||
code = ''.join([str(random.randint(1, 9) % 10) for _ in range(4)])
|
||||
ContractSignatureCode.objects.create(
|
||||
code=code,
|
||||
signature=self,
|
||||
expiration_time = timezone.now() + timedelta(minutes=2)
|
||||
)
|
||||
return code
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'contract signature'
|
||||
verbose_name = 'contract signatures'
|
||||
verbose_name_plural = 'contract signatures'
|
||||
db_table = 'contract_signatures'
|
||||
unique_together = ['contract', 'user']
|
||||
unique_together = ['contract', 'contract_side']
|
||||
|
||||
|
||||
class ContractSignatureCode(BaseModel):
|
||||
code = models.PositiveSmallIntegerField()
|
||||
signature = models.ForeignKey(ContractSignature, on_delete=models.CASCADE, related_name='signature_codes')
|
||||
expiration_time = models.DateTimeField()
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.code} - {self.signature}'
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'contract signature code'
|
||||
verbose_name_plural = 'contract signature codes'
|
||||
db_table = 'contract_signature_codes'
|
||||
|
||||
@@ -47,5 +47,5 @@ class ContractDetailSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Contract
|
||||
fields = [
|
||||
'id', 'name', 'file', 'contract_number', 'contract_sides',
|
||||
'id', 'name', 'file', 'status', 'contract_number', 'contract_sides',
|
||||
]
|
||||
@@ -2,8 +2,9 @@ from django.contrib.auth import get_user_model
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from core.apps.contracts.models.contract import ContractSide, Contract
|
||||
from core.apps.contracts.models.contract import ContractSide, Contract, ContractSignature
|
||||
from core.apps.contracts.enums.contract_side import ROLE
|
||||
from core.apps.contracts.serializers.contract_signature import ContractSignatureListSerializer
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@@ -25,8 +26,14 @@ class ContractSideCreateSerializer(serializers.Serializer):
|
||||
|
||||
|
||||
class ContractSideListSerializer(serializers.ModelSerializer):
|
||||
contract_signature = serializers.SerializerMethodField(method_name='get_contract_signature')
|
||||
|
||||
class Meta:
|
||||
model = ContractSide
|
||||
fields = [
|
||||
'id', 'full_name', 'user'
|
||||
]
|
||||
'id', 'full_name', 'user', 'contract_signature'
|
||||
]
|
||||
|
||||
def get_contract_signature(self, obj):
|
||||
contract_signature = obj.contract_signatures
|
||||
return ContractSignatureListSerializer(contract_signature).data
|
||||
34
core/apps/contracts/serializers/contract_signature.py
Normal file
34
core/apps/contracts/serializers/contract_signature.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from django.utils import timezone
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from core.apps.contracts.models.contract import ContractSignature, ContractSignatureCode
|
||||
|
||||
|
||||
class ContractSignatureListSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ContractSignature
|
||||
fields = [
|
||||
'id', 'status', 'signature_type', 'is_signature'
|
||||
]
|
||||
|
||||
|
||||
class ContractSignatureSerializer(serializers.Serializer):
|
||||
code = serializers.IntegerField()
|
||||
signature_id = serializers.UUIDField()
|
||||
|
||||
def validate(self, data):
|
||||
user = self.context.get('user')
|
||||
signature = ContractSignature.objects.filter(id=data.get('signature_id')).first()
|
||||
if not signature:
|
||||
raise serializers.ValidationError({"detail": "contract signature not found"})
|
||||
if signature.contract_side.user != user:
|
||||
raise serializers.ValidationError({'detail': 'this is not your code'})
|
||||
signature_code = ContractSignatureCode.objects.filter(signature=signature, code=data.get('code')).first()
|
||||
if not signature_code:
|
||||
raise serializers.ValidationError({'detail': 'invalid code'})
|
||||
if signature_code.expiration_time < timezone.now():
|
||||
raise serializers.ValidationError({"detail": 'code is expired'})
|
||||
data['contract'] = signature.contract_side.contract
|
||||
data['contract_signature'] = signature
|
||||
return data
|
||||
@@ -3,6 +3,8 @@ from django.contrib.auth import get_user_model
|
||||
from celery import shared_task
|
||||
|
||||
from core.apps.contracts.models.contract import ContractSide, Contract, ContractSignature
|
||||
from core.services.sms_via_bot import send_sms_code
|
||||
|
||||
|
||||
@shared_task
|
||||
def create_contract_side(data):
|
||||
@@ -11,7 +13,7 @@ def create_contract_side(data):
|
||||
contract = Contract.objects.get(id=data['contract_id'])
|
||||
user = User.objects.get(phone=data['phone'])
|
||||
|
||||
ContractSide.objects.create(
|
||||
contract_side = ContractSide.objects.create(
|
||||
full_name=data.get('full_name'),
|
||||
indentification=data.get('indentification'),
|
||||
position=data.get('position'),
|
||||
@@ -23,5 +25,7 @@ def create_contract_side(data):
|
||||
|
||||
ContractSignature.objects.create(
|
||||
contract=contract,
|
||||
user=user,
|
||||
)
|
||||
contract_side=contract_side,
|
||||
)
|
||||
|
||||
|
||||
|
||||
14
core/apps/contracts/tasks/contract_signature.py
Normal file
14
core/apps/contracts/tasks/contract_signature.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from celery import shared_task
|
||||
|
||||
from core.apps.contracts.models.contract import ContractSignature, ContractSignatureCode
|
||||
from core.services.sms_via_bot import send_sms_code
|
||||
|
||||
|
||||
@shared_task
|
||||
def send_contract_signature_code(signature_id):
|
||||
contract_signature = get_object_or_404(ContractSignature, id=signature_id)
|
||||
code = contract_signature.generate_code()
|
||||
send_sms_code(code, 'contract', contract_signature.contract_side.user.phone)
|
||||
|
||||
@@ -2,6 +2,7 @@ from django.urls import path, include
|
||||
|
||||
from core.apps.contracts.views import contract as contract_views
|
||||
from core.apps.contracts.views import contract_side as contract_side_views
|
||||
from core.apps.contracts.views import contract_signature as contract_signature_views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
@@ -15,5 +16,11 @@ urlpatterns = [
|
||||
path('contract_side/', include([
|
||||
path('create/', contract_side_views.ConstartSideCreateApiView.as_view(), name='contract-side-create'),
|
||||
]
|
||||
)),
|
||||
path('contract_signature/', include(
|
||||
[
|
||||
path('send_signature_code/<uuid:signature_id>/', contract_signature_views.SendContractSignatureCodeApiView.as_view(), name='send-signature-code'),
|
||||
path('sign_contract/', contract_signature_views.SigningContractApiView.as_view(), name='sign-contract'),
|
||||
]
|
||||
))
|
||||
]
|
||||
44
core/apps/contracts/views/contract_signature.py
Normal file
44
core/apps/contracts/views/contract_signature.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from rest_framework import generics, status, permissions, views
|
||||
from rest_framework.response import Response
|
||||
|
||||
from core.apps.contracts.models.contract import ContractSignature, ContractSignatureCode
|
||||
from core.apps.contracts.serializers.contract_signature import ContractSignatureSerializer
|
||||
from core.apps.contracts.tasks.contract_signature import send_contract_signature_code
|
||||
|
||||
|
||||
class SendContractSignatureCodeApiView(views.APIView):
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get(self, request, signature_id):
|
||||
# TODO: create and send code with celery in backgroud
|
||||
send_contract_signature_code.delay(signature_id)
|
||||
return Response({"success": True, "message": "code send"}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class SigningContractApiView(generics.GenericAPIView):
|
||||
serializer_class = ContractSignatureSerializer
|
||||
queryset = ContractSignature.objects.all()
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
user = request.user
|
||||
serializer = self.serializer_class(data=request.data, context={'user': user})
|
||||
if serializer.is_valid():
|
||||
data = serializer.validated_data
|
||||
contract = data.get('contract')
|
||||
contract_signature = data.get('contract_signature')
|
||||
if contract.company == user:
|
||||
if contract.status == 'created':
|
||||
contract.status = 'signed_company'
|
||||
elif contract.status == 'signed_customer':
|
||||
contract.status = 'signed_contract'
|
||||
else:
|
||||
if contract.status == 'created':
|
||||
contract.status = 'signed_customer'
|
||||
elif contract.status == 'signed_company':
|
||||
contract.status = 'signed_contract'
|
||||
contract_signature.status = 'signed'
|
||||
contract_signature.save()
|
||||
contract.save()
|
||||
return Response({'success': True, 'message': 'contract is signed'}, status=status.HTTP_200_OK)
|
||||
return Response({'success': False, 'message': serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
|
||||
Reference in New Issue
Block a user