add product
This commit is contained in:
1 parent
423d4cf40e
commit
c8d45dd07c
58 files changed
+8309
-674
No files matched your search
+20
-7
@@ -1,3 +1,4 @@
|
||||
from utils.admin_base import CRMModelAdmin
|
||||
from django.contrib import admin
|
||||
from apps.customer.models import (
|
||||
Customer as CustomerModel,
|
||||
@@ -11,36 +12,48 @@ from apps.customer.models import (
|
||||
# Register your models here.
|
||||
|
||||
|
||||
class CustomerNoteInline(admin.TabularInline):
|
||||
model = CustomerNoteModel
|
||||
extra = 0
|
||||
fields = ["desc","cuser_role","created_at"]
|
||||
readonly_fields = fields
|
||||
can_delete = False
|
||||
show_change_link = True
|
||||
|
||||
def has_add_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
|
||||
@admin.register(CustomerModel)
|
||||
class CustomerAdmin(admin.ModelAdmin):
|
||||
class CustomerAdmin(CRMModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(CustomerNoteModel)
|
||||
class CustomerNoteAdmin(admin.ModelAdmin):
|
||||
class CustomerNoteAdmin(CRMModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(ShiveAshenaeiModel)
|
||||
class ShiveAshenaeiAdmin(admin.ModelAdmin):
|
||||
class ShiveAshenaeiAdmin(CRMModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(ResponsibleGroupModel)
|
||||
class ResponsibleGroupAdmin(admin.ModelAdmin):
|
||||
class ResponsibleGroupAdmin(CRMModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(CustomerResponsibleModel)
|
||||
class CustomerResponsibleAdmin(admin.ModelAdmin):
|
||||
class CustomerResponsibleAdmin(CRMModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(CustomerGroupTypeModel)
|
||||
class CustomerGroupTypeAdmin(admin.ModelAdmin):
|
||||
class CustomerGroupTypeAdmin(CRMModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(CustomerGroupModel)
|
||||
class CustomerGroupAdmin(admin.ModelAdmin):
|
||||
class CustomerGroupAdmin(CRMModelAdmin):
|
||||
pass
|
||||
@@ -0,0 +1,60 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def canonical_mobile(value):
|
||||
if value is None:
|
||||
return None
|
||||
text = str(value)
|
||||
if len(text) == 12 and text.startswith('98'):
|
||||
return int(text[2:])
|
||||
return value
|
||||
|
||||
|
||||
def normalized_rows(model, alias, per_workspace=False):
|
||||
seen = {}
|
||||
updates = []
|
||||
fields = ('pk', 'mobile', 'sys_ws_id') if per_workspace else ('pk', 'mobile')
|
||||
for row in model.objects.using(alias).only(*fields).iterator():
|
||||
mobile = canonical_mobile(row.mobile)
|
||||
key = (row.sys_ws_id, mobile) if per_workspace else mobile
|
||||
if mobile is not None and key in seen:
|
||||
raise RuntimeError(
|
||||
f'Duplicate mobiles in {model._meta.label}: record IDs {seen[key]} and {row.pk}. '
|
||||
'Resolve these records before migration. No records were deleted.'
|
||||
)
|
||||
if mobile is not None:
|
||||
seen[key] = row.pk
|
||||
if mobile != row.mobile:
|
||||
row.mobile = mobile
|
||||
updates.append(row)
|
||||
return updates
|
||||
|
||||
|
||||
def check_and_normalize_mobiles(apps, schema_editor):
|
||||
alias = schema_editor.connection.alias
|
||||
customer = apps.get_model('customer', 'Customer')
|
||||
user = apps.get_model('cuser', 'CUser')
|
||||
role = apps.get_model('cuser', 'CuserRole')
|
||||
# Audit both tables before writing. A collision never silently merges people.
|
||||
customer_updates = normalized_rows(customer, alias, per_workspace=True)
|
||||
user_updates = normalized_rows(user, alias)
|
||||
role_updates = []
|
||||
for row in role.objects.using(alias).only('pk', 'mobile').iterator():
|
||||
normalized = canonical_mobile(row.mobile)
|
||||
if normalized != row.mobile:
|
||||
row.mobile = normalized
|
||||
role_updates.append(row)
|
||||
customer.objects.using(alias).bulk_update(customer_updates, ['mobile'], batch_size=500)
|
||||
user.objects.using(alias).bulk_update(user_updates, ['mobile'], batch_size=500)
|
||||
role.objects.using(alias).bulk_update(role_updates, ['mobile'], batch_size=500)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [('customer', '0007_alter_customer_name_family'), ('cuser', '0013_alter_cuser_name_family')]
|
||||
operations = [
|
||||
migrations.RunPython(check_and_normalize_mobiles, migrations.RunPython.noop),
|
||||
migrations.AddConstraint(
|
||||
model_name='customer',
|
||||
constraint=models.UniqueConstraint(fields=('sys_ws', 'mobile'), name='customer_workspace_mobile_unique'),
|
||||
),
|
||||
]
|
||||
@@ -3,6 +3,7 @@ from utils.base_model import BaseModel
|
||||
from apps.workspace.models import City as CityModel, Workspace as WorkspaceModel
|
||||
from apps.cuser.models import CuserRole as CuserRoleModel
|
||||
from django.core.exceptions import ValidationError
|
||||
from utils.validations.mobile import normalize_mobile
|
||||
# Create your models here.
|
||||
|
||||
|
||||
@@ -61,13 +62,24 @@ class Customer(BaseModel):
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created_at']
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['sys_ws', 'mobile'], name='customer_workspace_mobile_unique'),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name_family)
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if self.mobile is not None:
|
||||
self.mobile = normalize_mobile(self.mobile)
|
||||
if type(self).objects.filter(sys_ws_id=self.sys_ws_id, mobile=self.mobile).exclude(pk=self.pk).exists():
|
||||
raise ValidationError({"mobile": "مشتری با این شماره موبایل در این فضای کاری قبلاً ثبت شده است."})
|
||||
if self.cuser_role and not self.cuser_role.role_type.role_name_en == 'workspace_customer':
|
||||
raise ValidationError("cuser_role type must be customer")
|
||||
if self.cuser_role and (self.cuser_role.sys_ws_id != self.sys_ws_id or not self.cuser_role.cuser_id
|
||||
or self.cuser_role.cuser.mobile != self.mobile):
|
||||
raise ValidationError({'cuser_role': 'حساب پنل باید متعلق به همین شماره موبایل و فضای کاری باشد.'})
|
||||
|
||||
|
||||
class CustomerNote(BaseModel):
|
||||
|
||||
+78
-52
@@ -10,7 +10,9 @@ from apps.cuser.models import CuserRole as CuserRoleModel
|
||||
from rest_framework import exceptions
|
||||
from utils.get_user_workspace import get_user_workspace, get_user_workspace_uuid
|
||||
from utils.get_cuser_role import get_cuser_role
|
||||
from django.db import transaction
|
||||
from django.db import transaction, IntegrityError
|
||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||
from utils.validations.mobile import normalize_mobile
|
||||
from django.contrib.auth import get_user_model
|
||||
import uuid
|
||||
import random
|
||||
@@ -20,11 +22,20 @@ CUser = get_user_model()
|
||||
|
||||
@transaction.atomic
|
||||
def customer_panel_access(request, mobile: int, customer_id: int, status: bool) -> CuserRoleModel:
|
||||
|
||||
try:
|
||||
workspace = get_user_workspace(request)
|
||||
customer = CustomerModel.objects.get(id=customer_id, sys_ws=workspace)
|
||||
|
||||
mobile = normalize_mobile(mobile)
|
||||
if customer.mobile != mobile:
|
||||
raise exceptions.ValidationError({'mobile': 'شماره موبایل باید با شماره مشتری یکسان باشد.'})
|
||||
# Disabling access must not create an unrelated account or a new role.
|
||||
if not status:
|
||||
if customer.cuser_role_id:
|
||||
if customer.cuser_role.sys_ws_id != workspace.pk:
|
||||
raise exceptions.ValidationError('اتصال پنل متعلق به این فضای کاری نیست؛ ابتدا اتصال را اصلاح کنید.')
|
||||
customer.cuser_role.customer_panel = False
|
||||
customer.cuser_role.save(update_fields=['customer_panel'])
|
||||
return customer.cuser_role
|
||||
try:
|
||||
cuser_obj = CUser.objects.get(mobile=mobile)
|
||||
except CUser.DoesNotExist:
|
||||
@@ -36,29 +47,29 @@ def customer_panel_access(request, mobile: int, customer_id: int, status: bool)
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
try:
|
||||
cuser_role_obj = CuserRoleModel.objects.get(cuser=cuser_obj, role_type_id=3)
|
||||
cuser_role_obj.customer_panel = status
|
||||
cuser_role_obj.save()
|
||||
except CuserRoleModel.DoesNotExist:
|
||||
cuser_role_obj = CuserRoleModel.objects.create(
|
||||
cuser=cuser_obj,
|
||||
role_type_id=3,
|
||||
name_family=customer.name_family,
|
||||
mobile=mobile,
|
||||
mobile_verify=True,
|
||||
is_active=True,
|
||||
customer_panel=status,
|
||||
sys_ws=workspace
|
||||
)
|
||||
|
||||
if cuser_role_obj:
|
||||
customer.cuser_role = cuser_role_obj
|
||||
customer.save()
|
||||
return cuser_role_obj
|
||||
else:
|
||||
raise Exception
|
||||
|
||||
from apps.cuser.models import RoleType
|
||||
if not cuser_obj.is_active:
|
||||
raise exceptions.PermissionDenied('حساب کاربری مربوط به این شماره غیرفعال است.')
|
||||
role_type = RoleType.objects.get(role_name_en='workspace_customer')
|
||||
cuser_role_obj, _ = CuserRoleModel.objects.get_or_create(
|
||||
cuser=cuser_obj, role_type=role_type, sys_ws=workspace,
|
||||
defaults={'name_family': customer.name_family, 'mobile': mobile,
|
||||
'mobile_verify': True, 'is_active': True, 'customer_panel': True},
|
||||
)
|
||||
if CustomerModel.objects.filter(cuser_role=cuser_role_obj, sys_ws=workspace).exclude(pk=customer.pk).exists():
|
||||
raise exceptions.ValidationError({'mobile': 'این شماره قبلاً به پنل مشتری دیگری متصل شده است.'})
|
||||
cuser_role_obj.customer_panel = True
|
||||
cuser_role_obj.save(update_fields=['customer_panel'])
|
||||
customer.cuser_role = cuser_role_obj
|
||||
customer.full_clean()
|
||||
customer.save()
|
||||
return cuser_role_obj
|
||||
except exceptions.APIException:
|
||||
raise
|
||||
except DjangoValidationError as exp:
|
||||
raise exceptions.ValidationError(exp.message_dict)
|
||||
except IntegrityError:
|
||||
raise exceptions.ValidationError({'mobile': 'این شماره موبایل قبلاً ثبت شده است.'})
|
||||
except Exception as exp:
|
||||
message = f'Error Create or Update Customer panel accsess: {exp}'
|
||||
raise exceptions.ValidationError({'error': message})
|
||||
@@ -104,6 +115,7 @@ def delete_shive_ashenaei(shive_id: int, workspace_uuid: str) -> ShiveAshenaeiMo
|
||||
raise exceptions.ValidationError({'error': message})
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def create_customer(name_family: str , mobile: int, phone: int, address: str, city: int, shive_ashenaei: int,
|
||||
responsible_group: int, cuser_role: int = None, workspace: WorkspaceModel = None) -> CustomerModel:
|
||||
try:
|
||||
@@ -122,39 +134,48 @@ def create_customer(name_family: str , mobile: int, phone: int, address: str, ci
|
||||
customer_obj.full_clean()
|
||||
customer_obj.save()
|
||||
return customer_obj
|
||||
except DjangoValidationError as exp:
|
||||
raise exceptions.ValidationError(exp.message_dict)
|
||||
except IntegrityError:
|
||||
raise exceptions.ValidationError({'mobile': 'مشتری با این شماره موبایل قبلاً ثبت شده است.'})
|
||||
except Exception as exp:
|
||||
message = f'Error Creating Customer: {exp}'
|
||||
raise exceptions.ValidationError({'error': message})
|
||||
|
||||
|
||||
def update_customer(customer_id: int, name_family: str = None , mobile: int = None, phone: int = None, address: str = None,
|
||||
def set_customer_mobile(customer, mobile):
|
||||
if mobile is None:
|
||||
return
|
||||
normalized = normalize_mobile(mobile)
|
||||
if normalized != customer.mobile and customer.cuser_role_id:
|
||||
role = customer.cuser_role
|
||||
if role.sys_ws_id == customer.sys_ws_id:
|
||||
role.customer_panel = False
|
||||
role.save(update_fields=['customer_panel'])
|
||||
customer.cuser_role = None
|
||||
customer.mobile = normalized
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def update_customer(customer_id: int, name_family: str = None, mobile: int = None, phone: int = None, address: str = None,
|
||||
city: int = None, shive_ashenaei: int = None, responsible_group: int = None, cuser_role: int = None, workspace_uuid: str = None) -> CustomerModel:
|
||||
try:
|
||||
customer_obj = CustomerModel.objects.get(id=customer_id, sys_ws__uuid=workspace_uuid)
|
||||
|
||||
if name_family is not None:
|
||||
customer_obj.name_family = name_family
|
||||
if mobile is not None:
|
||||
customer_obj.mobile = mobile
|
||||
if phone is not None:
|
||||
customer_obj.phone = phone
|
||||
if address is not None:
|
||||
customer_obj.address = address
|
||||
if city is not None:
|
||||
customer_obj.city = city
|
||||
if shive_ashenaei is not None:
|
||||
customer_obj.shive_ashenaei = shive_ashenaei
|
||||
if responsible_group is not None:
|
||||
customer_obj.responsible_group = responsible_group
|
||||
if cuser_role is not None:
|
||||
customer_obj.cuser_role = cuser_role
|
||||
|
||||
customer_obj.full_clean()
|
||||
customer_obj.save()
|
||||
return customer_obj
|
||||
except Exception as exp:
|
||||
message = f'Error Creating Customer: {exp}'
|
||||
raise exceptions.ValidationError({'error': message})
|
||||
customer = CustomerModel.objects.select_for_update().get(id=customer_id, sys_ws__uuid=workspace_uuid)
|
||||
set_customer_mobile(customer, mobile)
|
||||
fields = {'name_family': name_family, 'phone': phone, 'address': address, 'city': city,
|
||||
'shive_ashenaei': shive_ashenaei, 'responsible_group': responsible_group, 'cuser_role': cuser_role}
|
||||
for field, value in fields.items():
|
||||
if value is not None:
|
||||
setattr(customer, field, value)
|
||||
customer.full_clean()
|
||||
customer.save()
|
||||
return customer
|
||||
except DjangoValidationError as error:
|
||||
raise exceptions.ValidationError(error.message_dict)
|
||||
except IntegrityError:
|
||||
raise exceptions.ValidationError({'mobile': 'مشتری با این شماره موبایل قبلاً ثبت شده است.'})
|
||||
except CustomerModel.DoesNotExist:
|
||||
raise exceptions.NotFound('مشتری در این فضای کاری پیدا نشد.')
|
||||
|
||||
|
||||
def delete_customer(customer_id: int, workspace_uuid: str = None) -> CustomerModel:
|
||||
@@ -165,6 +186,7 @@ def delete_customer(customer_id: int, workspace_uuid: str = None) -> CustomerMod
|
||||
raise exceptions.ValidationError({'error': message})
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def create_customer_v2(request, name_family: str , mobile: int, phone: int, address: str, city: int, shive_ashenaei: int,
|
||||
responsible_group: int, cuser_role: int = None) -> CustomerModel:
|
||||
try:
|
||||
@@ -186,6 +208,10 @@ def create_customer_v2(request, name_family: str , mobile: int, phone: int, addr
|
||||
customer_obj.full_clean()
|
||||
customer_obj.save()
|
||||
return customer_obj
|
||||
except DjangoValidationError as exp:
|
||||
raise exceptions.ValidationError(exp.message_dict)
|
||||
except IntegrityError:
|
||||
raise exceptions.ValidationError({'mobile': 'مشتری با این شماره موبایل قبلاً ثبت شده است.'})
|
||||
except Exception as exp:
|
||||
message = f'Error Creating Customer: {exp}'
|
||||
raise exceptions.ValidationError({'error': message})
|
||||
|
||||
@@ -13,6 +13,17 @@ from apps.workspace.models import City as CityModel
|
||||
from utils.tools import inline_model_serializer, inline_serializer
|
||||
from utils.pagination import CustomPagination
|
||||
from django_filters import rest_framework as filters
|
||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||
from utils.validations.mobile import normalize_mobile
|
||||
|
||||
|
||||
class MobileField(serializers.CharField):
|
||||
def to_internal_value(self, data):
|
||||
value = super().to_internal_value(data)
|
||||
try:
|
||||
return normalize_mobile(value)
|
||||
except DjangoValidationError:
|
||||
raise serializers.ValidationError('شماره موبایل معتبر وارد کنید.')
|
||||
|
||||
|
||||
|
||||
@@ -87,6 +98,7 @@ class CustomerCreateApi(APIView):
|
||||
permission_classes = (IsAuthenticated, custom_permissions.WorkspaceOperatorPermission)
|
||||
|
||||
class InputSerializer(serializers.ModelSerializer):
|
||||
mobile = MobileField(required=False, allow_null=True)
|
||||
class Meta:
|
||||
model = CustomerModel
|
||||
fields = '__all__'
|
||||
@@ -114,6 +126,7 @@ class CustomerUpdateApi(APIView):
|
||||
permission_classes = (IsAuthenticated, custom_permissions.WorkspaceOperatorPermission)
|
||||
|
||||
class InputSerializer(serializers.ModelSerializer):
|
||||
mobile = MobileField(required=False, allow_null=True)
|
||||
class Meta:
|
||||
model = CustomerModel
|
||||
fields = '__all__'
|
||||
@@ -125,7 +138,7 @@ class CustomerUpdateApi(APIView):
|
||||
exclude = ('sys_ws', )
|
||||
|
||||
def patch(self, request, customer_id: int):
|
||||
serializer = self.InputSerializer(data=request.data)
|
||||
serializer = self.InputSerializer(data=request.data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
user_workspace_uuid = get_user_workspace_uuid(request=request)
|
||||
@@ -215,7 +228,7 @@ class CustomerCreateApiV2(APIView):
|
||||
|
||||
class InputSerializer(serializers.Serializer):
|
||||
name_family = serializers.CharField(max_length=25, required=True)
|
||||
mobile = serializers.IntegerField(required=True)
|
||||
mobile = MobileField(required=True)
|
||||
phone = serializers.IntegerField(required=True)
|
||||
address = serializers.CharField(max_length=300, required=True)
|
||||
city = serializers.IntegerField(required=True)
|
||||
|
||||
@@ -22,6 +22,6 @@ class CustomerPanelApi(APIView):
|
||||
|
||||
cuser_role = customer_panel_access(request=request, **serializer.validated_data)
|
||||
|
||||
return Response(data={'cuser_role_id': cuser_role.id}, status=status.HTTP_200_OK)
|
||||
return Response(data={'cuser_role_id': cuser_role.id if cuser_role else None}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
Reference in new issue
Block a user