feat(projects): add project-specific member rates

This commit is contained in:
2026-05-23 18:29:00 +03:30
parent b79fd73403
commit 181a135df9
8 changed files with 381 additions and 22 deletions

View File

@@ -16,9 +16,11 @@ from apps.projects.models import Project
from apps.projects.api.serializers import (
ProjectSerializer, ProjectCreateSerializer, ProjectUpdateSerializer,
ProjectAccessMutationSerializer, ProjectAccessQuerySerializer,
ProjectAccessRateMutationSerializer,
)
from apps.projects.api.permissions import IsProjectMember, IsProjectManager
from apps.projects.services.access import (
build_project_access_item,
build_project_access_items,
ensure_workspace_project_access,
filter_projects_for_user,
@@ -26,6 +28,7 @@ from apps.projects.services.access import (
grant_project_accesses,
revoke_project_accesses,
)
from apps.projects.services.rates import get_current_project_user_rate, remove_project_user_rate, upsert_project_user_rate
from apps.projects.services.projects import (
create_project,
update_project,
@@ -231,3 +234,54 @@ class ProjectViewSet(ModelViewSet):
project_ids=[str(project_id) for project_id in serializer.validated_data["project_ids"]],
)
return Response({"changed": changed}, status=status.HTTP_200_OK)
@action(detail=False, methods=["post"], url_path="access/rate")
def set_access_rate(self, request):
serializer = ProjectAccessRateMutationSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
workspace = get_object_or_404(
Workspace,
id=serializer.validated_data["workspace"],
is_deleted=False,
)
ensure_workspace_project_access(request.user, workspace)
membership = get_access_managed_membership(workspace, str(serializer.validated_data["user"]))
project = get_object_or_404(
Project,
id=serializer.validated_data["project"],
workspace=workspace,
is_deleted=False,
)
has_access = membership.user.project_accesses.filter(project=project).exists()
if not has_access:
return Response(
{"detail": "Grant project access before setting a project-specific rate."},
status=status.HTTP_400_BAD_REQUEST,
)
removed = serializer.validated_data.get("hourly_rate") is None
if removed:
remove_project_user_rate(project=project, user=membership.user)
else:
upsert_project_user_rate(
project=project,
user=membership.user,
hourly_rate=serializer.validated_data["hourly_rate"],
currency=serializer.validated_data.get("currency", "USD"),
)
workspace_rate = (
workspace.user_rates.filter(user=membership.user, is_deleted=False)
.order_by("-effective_from", "-updated_at")
.first()
)
project_rate = get_current_project_user_rate(project=project, user=membership.user)
item = build_project_access_item(
project=project,
has_access=True,
workspace_rate=workspace_rate,
project_rate=project_rate,
)
return Response({"removed": removed, "item": item}, status=status.HTTP_200_OK)