diff --git a/config/urls.py b/config/urls.py index cfdf544..69d4969 100644 --- a/config/urls.py +++ b/config/urls.py @@ -33,4 +33,4 @@ urlpatterns = [ # Media and Static Files urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) -urlpatterns += [path('silk/', include('silk.urls', namespace='silk'))] +# urlpatterns += [path('silk/', include('silk.urls', namespace='silk'))] diff --git a/core/apps/accounts/management/commands/seed_permissions.py b/core/apps/accounts/management/commands/seed_permissions.py index 4fcd1ec..df5d03e 100644 --- a/core/apps/accounts/management/commands/seed_permissions.py +++ b/core/apps/accounts/management/commands/seed_permissions.py @@ -8,10 +8,10 @@ class Command(BaseCommand): def handle(self, *args, **options): permissions = [ - {"code": "can_see_product_wherehouse", "name": "permission for see wherehouse list"}, + {"code": "project", "name": "project all access"}, { - "code": "can_add_product_wherehouse", - "name": "permission for add product in wherehouse" + "code": "project_folder", + "name": "project folder all access" } ] diff --git a/core/apps/accounts/migrations/0003_alter_role_permissions.py b/core/apps/accounts/migrations/0003_alter_role_permissions.py new file mode 100644 index 0000000..4189718 --- /dev/null +++ b/core/apps/accounts/migrations/0003_alter_role_permissions.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.4 on 2025-08-05 10:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0002_permission_role_user_role'), + ] + + operations = [ + migrations.AlterField( + model_name='role', + name='permissions', + field=models.ManyToManyField(blank=True, related_name='roles', to='accounts.permission'), + ), + ] diff --git a/core/apps/accounts/models/role.py b/core/apps/accounts/models/role.py index 4181bf1..4647191 100644 --- a/core/apps/accounts/models/role.py +++ b/core/apps/accounts/models/role.py @@ -7,7 +7,7 @@ from core.apps.accounts.models.permission import Permission class Role(BaseModel): name = models.CharField(max_length=200, unique=True) - permissions = models.ManyToManyField(Permission, related_name='roles') + permissions = models.ManyToManyField(Permission, related_name='roles', blank=True) def __str__(self): return self.name diff --git a/core/apps/projects/migrations/0006_remove_project_cash_transaction_and_more.py b/core/apps/projects/migrations/0006_remove_project_cash_transaction_and_more.py new file mode 100644 index 0000000..0cce723 --- /dev/null +++ b/core/apps/projects/migrations/0006_remove_project_cash_transaction_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.2.4 on 2025-08-05 09:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('finance', '0002_paymenttype_cashtransaction_employees_and_more'), + ('projects', '0005_builder_project_area_project_benifit_plan_and_more'), + ('wherehouse', '0002_stockmovemend'), + ] + + operations = [ + migrations.RemoveField( + model_name='project', + name='cash_transaction', + ), + migrations.RemoveField( + model_name='project', + name='wherehouse', + ), + migrations.AddField( + model_name='project', + name='cash_transaction', + field=models.ManyToManyField(null=True, related_name='projects', to='finance.cashtransaction'), + ), + migrations.AddField( + model_name='project', + name='wherehouse', + field=models.ManyToManyField(related_name='projects', to='wherehouse.wherehouse'), + ), + ] diff --git a/core/apps/projects/migrations/0007_alter_project_cash_transaction.py b/core/apps/projects/migrations/0007_alter_project_cash_transaction.py new file mode 100644 index 0000000..8162feb --- /dev/null +++ b/core/apps/projects/migrations/0007_alter_project_cash_transaction.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.4 on 2025-08-05 09:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('finance', '0002_paymenttype_cashtransaction_employees_and_more'), + ('projects', '0006_remove_project_cash_transaction_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='project', + name='cash_transaction', + field=models.ManyToManyField(related_name='projects', to='finance.cashtransaction'), + ), + ] diff --git a/core/apps/projects/migrations/0008_projectfolder_color.py b/core/apps/projects/migrations/0008_projectfolder_color.py new file mode 100644 index 0000000..e8a2f13 --- /dev/null +++ b/core/apps/projects/migrations/0008_projectfolder_color.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.4 on 2025-08-05 09:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0007_alter_project_cash_transaction'), + ] + + operations = [ + migrations.AddField( + model_name='projectfolder', + name='color', + field=models.CharField(choices=[('ORANGE', 'orange'), ('GREEN', 'green'), ('BLUE', 'blue'), ('PURPLE', 'purple'), ('RED', 'red')], default='ORANGE', max_length=10), + ), + ] diff --git a/core/apps/projects/migrations/0009_project_status.py b/core/apps/projects/migrations/0009_project_status.py new file mode 100644 index 0000000..0ef5b1d --- /dev/null +++ b/core/apps/projects/migrations/0009_project_status.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.4 on 2025-08-05 10:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0008_projectfolder_color'), + ] + + operations = [ + migrations.AddField( + model_name='project', + name='status', + field=models.CharField(choices=[('PLANNED', 'planned'), ('IN_PROGRESS', 'in progress'), ('FINISHED', 'finished'), ('SUSPENDED', 'suspended')], default='PLANNED', max_length=20), + ), + ] diff --git a/core/apps/projects/models/project.py b/core/apps/projects/models/project.py index cef996e..baf5835 100644 --- a/core/apps/projects/models/project.py +++ b/core/apps/projects/models/project.py @@ -9,7 +9,15 @@ from core.apps.finance.models import CashTransaction class ProjectFolder(BaseModel): + COLORS = ( + ('ORANGE', 'orange'), + ('GREEN', 'green'), + ('BLUE', 'blue'), + ('PURPLE', 'purple'), + ('RED', 'red') + ) name = models.CharField(max_length=200) + color = models.CharField(max_length=10, choices=COLORS, default='ORANGE') def __str__(self): return self.name @@ -20,6 +28,13 @@ class ProjectFolder(BaseModel): class Project(BaseModel): + STATUS = ( + ('PLANNED', 'planned'), + ('IN_PROGRESS', 'in progress'), + ('FINISHED', 'finished'), + ('SUSPENDED', 'suspended'), + ) + name = models.CharField(max_length=200) location = models.CharField(max_length=200) start_date = models.DateField() @@ -42,10 +57,11 @@ class Project(BaseModel): WhereHouse, related_name='projects' ) cash_transaction = models.ManyToManyField( - CashTransaction, related_name='projects', null=True + CashTransaction, related_name='projects' ) currency = models.CharField(choices=[('usd', 'usd'),('uzs','uzs')], max_length=3, default='uzs') benifit_plan = models.PositiveBigIntegerField(null=True) + status = models.CharField(max_length=20, choices=STATUS, default='PLANNED') def __str__(self): return self.name diff --git a/core/apps/projects/serializers/project.py b/core/apps/projects/serializers/project.py index a307817..e9a7c82 100644 --- a/core/apps/projects/serializers/project.py +++ b/core/apps/projects/serializers/project.py @@ -17,7 +17,7 @@ class ProjectDetailSerialzier(serializers.ModelSerializer): class Meta: model = Project fields = [ - 'id', 'name', 'location', 'start_date', 'end_date', + 'id', 'name', 'location', 'start_date', 'end_date', 'status', 'benifit_plan' ] @@ -27,16 +27,47 @@ class ProjectCreateSerializer(serializers.Serializer): end_date = serializers.DateField() name = serializers.CharField() + builder_id = serializers.UUIDField() + area = serializers.IntegerField() + + boss = serializers.ListSerializer(child=serializers.UUIDField()) + foreman = serializers.ListSerializer(child=serializers.UUIDField()) + other_members = serializers.ListSerializer(child=serializers.UUIDField()) + + wherehouse = serializers.ListSerializer(child=serializers.UUIDField()) + cash_transaction = serializers.ListSerializer(child=serializers.UUIDField()) + currency = serializers.ChoiceField(choices=[('uzs', 'uzs'), ('usd', 'usd')]) + benifit_plan = serializers.IntegerField() + def create(self, validated_data): + boss_ids = validated_data.pop('boss') + foreman_ids = validated_data.pop('foreman') + other_member_ids = validated_data.pop('other_members') + warehouse_ids = validated_data.pop('wherehouse') + cash_transaction_ids = validated_data.pop('cash_transaction') + builder_id = validated_data.pop('builder_id') + with transaction.atomic(): - return Project.objects.create( + project = Project.objects.create( name=validated_data.get('name'), start_date=validated_data.get('start_date'), end_date=validated_data.get('end_date'), - location=validated_data.get('location') + location=validated_data.get('location'), + area=validated_data.get('area'), + currency=validated_data.get('currency'), + benifit_plan=validated_data.get('benifit_plan'), + builder_id=builder_id ) + project.boss.set(boss_ids) + project.foreman.set(foreman_ids) + project.other_members.set(other_member_ids) + project.wherehouse.set(warehouse_ids) + project.cash_transaction.set(cash_transaction_ids) + return project + +# Project Folder class ProjectFolderCreateSerializer(serializers.Serializer): name = serializers.CharField() @@ -80,3 +111,30 @@ class ProjectFolderProjectCreateSerializer(serializers.Serializer): end_date=validated_data.get('end_date') ) + +class ProjectFolderUpdateSerializer(serializers.ModelSerializer): + class Meta: + model = ProjectFolder + fields = [ + 'name', 'color' + ] + + def update(self, instance, validated_data): + instance.name = validated_data.get('name', instance.name) + instance.color = validated_data.get('color', instance.color) + instance.save() + return instance + + +class ProjectFolderDetailSerializer(serializers.ModelSerializer): + projects = ProjectListSerializer(many=True) + projects_count = serializers.SerializerMethodField(method_name='get_projects_count') + + class Meta: + model = ProjectFolder + fields = [ + 'id', 'name', 'color', 'projects_count', 'projects' + ] + + def get_projects_count(self, obj): + return obj.projects.count() \ No newline at end of file diff --git a/core/apps/projects/urls.py b/core/apps/projects/urls.py index 24d6c28..8c60952 100644 --- a/core/apps/projects/urls.py +++ b/core/apps/projects/urls.py @@ -16,6 +16,9 @@ urlpatterns = [ path('create/', project_views.ProjectFolderCreateApiView.as_view()), path('list/', project_views.ProjectFolderListApiView.as_view()), path('create/project/', project_views.ProjectFolderCreateProjectApiView.as_view()), + path('/update/', project_views.ProjectFolderUpdateApiView.as_view()), + path('/', project_views.ProjectFolderDetailApiView.as_view()), + path('/delete/', project_views.ProjectFolderDeleteApiView.as_view()), ] )), path('builder/', include( diff --git a/core/apps/projects/views/project.py b/core/apps/projects/views/project.py index c445c5a..631ce68 100644 --- a/core/apps/projects/views/project.py +++ b/core/apps/projects/views/project.py @@ -1,4 +1,6 @@ -from rest_framework import generics, status +from django.shortcuts import get_object_or_404 + +from rest_framework import generics, status, views from rest_framework.response import Response from core.apps.projects.models.project import Project, ProjectFolder @@ -11,15 +13,18 @@ class ProjectListApiView(generics.ListAPIView): serializer_class = serializers.ProjectListSerializer queryset = Project.objects.all() permission_classes = [HasRolePermission] - required_permissions = [] + required_permissions = ['project'] pagination_class = CustomPageNumberPagination + def get_queryset(self): + return Project.objects.exclude(folder__isnull=False) + class ProjectDetailApiView(generics.RetrieveAPIView): serializer_class = serializers.ProjectDetailSerialzier queryset = Project.objects.all() permission_classes = [HasRolePermission] - required_permissions = [] + required_permissions = ['project'] lookup_field = 'id' @@ -27,7 +32,7 @@ class ProjectCreateApiView(generics.CreateAPIView): serializer_class = serializers.ProjectCreateSerializer queryset = Project.objects.all() permission_classes = [HasRolePermission] - required_permissions = [] + required_permissions = ['project'] # Project Folder @@ -35,14 +40,14 @@ class ProjectFolderCreateApiView(generics.CreateAPIView): serializer_class = serializers.ProjectFolderCreateSerializer queryset = ProjectFolder.objects.all() permission_classes = [HasRolePermission] - required_permissions = [] + required_permissions = ['project_folder'] class ProjectFolderListApiView(generics.ListAPIView): serializer_class = serializers.ProjectFolderListSerializer queryset = ProjectFolder.objects.prefetch_related('projects') permission_classes = [HasRolePermission] - required_permissions = [] + required_permissions = ['project_folder'] pagination_class = CustomPageNumberPagination @@ -50,4 +55,41 @@ class ProjectFolderCreateProjectApiView(generics.CreateAPIView): serializer_class = serializers.ProjectFolderProjectCreateSerializer queryset = Project.objects.all() permission_classes = [HasRolePermission] - required_permissions = [] \ No newline at end of file + required_permissions = ['project_folder'] + + +class ProjectFolderUpdateApiView(generics.GenericAPIView): + serializer_class = serializers.ProjectFolderUpdateSerializer + queryset = ProjectFolder.objects.all() + permission_classes = [HasRolePermission] + required_permissions = ['project_folder'] + + def put(self, request, id): + folder = get_object_or_404(ProjectFolder, id=id) + serializer = self.serializer_class(data=request.data, instance=folder) + if serializer.is_valid(): + serializer.save() + return Response({'success':True, 'message': "Folder updated!"}, status=200) + return Response({"success": False, 'message': serializer.errors}, status=400) + + +class ProjectFolderDetailApiView(generics.GenericAPIView): + serializer_class = serializers.ProjectFolderDetailSerializer + queryset = ProjectFolder.objects.select_related('projects') + permission_classes = [HasRolePermission] + required_permissions = ['project_folder'] + + def get(self, request, id): + folder = get_object_or_404(ProjectFolder, id=id) + serializer = self.serializer_class(folder) + return Response(serializer.data, status=200) + + +class ProjectFolderDeleteApiView(views.APIView): + permission_classes = [HasRolePermission] + required_permissions = ['project_folder'] + + def delete(self, request, id): + folder = get_object_or_404(ProjectFolder, id=id) + folder.delete() + return Response({"success": True, "message": 'deleted!'}, status=204) \ No newline at end of file