mirror of
https://github.com/makeplane/plane.git
synced 2026-07-14 06:25:58 +02:00
Merge branch 'preview' of github.com:makeplane/plane-ee into develop
This commit is contained in:
@@ -7,7 +7,7 @@ import jwt
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import validate_email
|
||||
from django.db.models import Count, F, Q
|
||||
from django.db.models import Count
|
||||
from django.utils import timezone
|
||||
|
||||
# Third party modules
|
||||
@@ -33,9 +33,6 @@ from plane.db.models import (
|
||||
from plane.utils.cache import invalidate_cache, invalidate_cache_directly
|
||||
from plane.payment.bgtasks.member_sync_task import member_sync_task
|
||||
from .. import BaseViewSet
|
||||
from plane.payment.utils.member_payment_count import (
|
||||
workspace_member_check,
|
||||
)
|
||||
|
||||
|
||||
class WorkspaceInvitationsViewset(BaseViewSet):
|
||||
|
||||
@@ -36,9 +36,6 @@ from plane.db.models import (
|
||||
)
|
||||
from plane.utils.cache import cache_response, invalidate_cache
|
||||
from plane.payment.bgtasks.member_sync_task import member_sync_task
|
||||
from plane.payment.utils.member_payment_count import (
|
||||
workspace_member_check,
|
||||
)
|
||||
from .. import BaseViewSet
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.14 on 2024-08-18 15:35
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ee', '0005_workspacelicense_has_activated_free_trial_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='workspacelicense',
|
||||
name='subscription',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
]
|
||||
@@ -55,6 +55,8 @@ class WorkspaceLicense(BaseModel):
|
||||
has_activated_free_trial = models.BooleanField(default=False)
|
||||
# is payment method added
|
||||
has_added_payment_method = models.BooleanField(default=False)
|
||||
# subscription
|
||||
subscription = models.CharField(max_length=255, null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Workspace License"
|
||||
|
||||
@@ -4,17 +4,15 @@ import requests
|
||||
# Django imports
|
||||
from django.conf import settings
|
||||
from django.db.models import F
|
||||
from django.utils import timezone
|
||||
|
||||
# Third party imports
|
||||
from celery import shared_task
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import WorkspaceMember, Workspace
|
||||
from plane.ee.models import WorkspaceLicense
|
||||
from plane.utils.exception_logger import log_exception
|
||||
from plane.payment.utils.workspace_license_request import (
|
||||
fetch_workspace_license,
|
||||
resync_workspace_license,
|
||||
)
|
||||
|
||||
|
||||
@@ -63,76 +61,7 @@ def member_sync_task(slug):
|
||||
response.raise_for_status()
|
||||
|
||||
# Refresh workspace license
|
||||
updated_workspace_license = fetch_workspace_license(
|
||||
workspace_id=workspace_id,
|
||||
workspace_slug=slug,
|
||||
free_seats=WorkspaceMember.objects.filter(
|
||||
is_active=True,
|
||||
workspace__slug=slug,
|
||||
member__is_bot=False,
|
||||
).count(),
|
||||
)
|
||||
|
||||
# Update workspace license
|
||||
workspace_license = WorkspaceLicense.objects.filter(
|
||||
workspace=workspace
|
||||
).first()
|
||||
|
||||
# If workspace license exists, update it
|
||||
if workspace_license:
|
||||
workspace_license.plane = updated_workspace_license["plan"]
|
||||
workspace_license.recurring_interval = (
|
||||
updated_workspace_license["interval"]
|
||||
)
|
||||
workspace_license.current_period_end_date = (
|
||||
updated_workspace_license["current_period_end_date"]
|
||||
)
|
||||
workspace_license.purchased_seats = updated_workspace_license[
|
||||
"purchased_seats"
|
||||
]
|
||||
workspace_license.free_seats = updated_workspace_license[
|
||||
"free_seats"
|
||||
]
|
||||
workspace_license.is_cancelled = updated_workspace_license[
|
||||
"is_cancelled"
|
||||
]
|
||||
workspace_license.is_offline_payment = (
|
||||
updated_workspace_license["is_offline_payment"]
|
||||
)
|
||||
workspace_license.trial_end_date = updated_workspace_license[
|
||||
"trial_end_date"
|
||||
]
|
||||
workspace_license.has_activated_free_trial = (
|
||||
updated_workspace_license["has_activated_free_trial"]
|
||||
)
|
||||
workspace_license.has_added_payment_method = (
|
||||
updated_workspace_license["has_added_payment_method"]
|
||||
)
|
||||
workspace_license.last_synced_at = timezone.now()
|
||||
workspace_license.save()
|
||||
|
||||
# Else create a new workspace license
|
||||
else:
|
||||
WorkspaceLicense.objects.create(
|
||||
workspace=workspace,
|
||||
plan=updated_workspace_license["plan"],
|
||||
recurring_interval=updated_workspace_license["interval"],
|
||||
current_period_end_date=updated_workspace_license[
|
||||
"current_period_end_date"
|
||||
],
|
||||
purchased_seats=updated_workspace_license["seats"],
|
||||
is_cancelled=updated_workspace_license["is_cancelled"],
|
||||
is_offline_payment=updated_workspace_license[
|
||||
"is_offline_payment"
|
||||
],
|
||||
last_synced_at=timezone.now(),
|
||||
has_activated_free_trial=updated_workspace_license[
|
||||
"has_activated_free_trial"
|
||||
],
|
||||
has_added_payment_method=updated_workspace_license[
|
||||
"has_added_payment_method"
|
||||
],
|
||||
)
|
||||
resync_workspace_license(slug, force=True)
|
||||
else:
|
||||
return
|
||||
except requests.exceptions.RequestException as e:
|
||||
|
||||
@@ -3,6 +3,11 @@ import requests
|
||||
|
||||
# Django imports
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import Workspace, WorkspaceMember
|
||||
from plane.ee.models import WorkspaceLicense
|
||||
|
||||
|
||||
def fetch_workspace_license(workspace_id, workspace_slug, free_seats=12):
|
||||
@@ -24,3 +29,126 @@ def fetch_workspace_license(workspace_id, workspace_slug, free_seats=12):
|
||||
response.raise_for_status()
|
||||
response = response.json()
|
||||
return response
|
||||
|
||||
|
||||
def resync_workspace_license(workspace_slug, force=False):
|
||||
# Fetch the workspace
|
||||
workspace = Workspace.objects.get(slug=workspace_slug)
|
||||
|
||||
# Check if the license is present for the workspace
|
||||
workspace_license = WorkspaceLicense.objects.filter(
|
||||
workspace=workspace
|
||||
).first()
|
||||
|
||||
# If the license is present, then check if the last sync is more than 1 hour
|
||||
if workspace_license:
|
||||
# If the last sync is more than 1 hour, then sync the license
|
||||
if (
|
||||
workspace_license.last_synced_at - timezone.now()
|
||||
).total_seconds() > 3600 or force:
|
||||
|
||||
# Fetch the workspace license
|
||||
response = fetch_workspace_license(
|
||||
workspace_id=str(workspace.id),
|
||||
workspace_slug=workspace_slug,
|
||||
free_seats=WorkspaceMember.objects.filter(
|
||||
is_active=True,
|
||||
workspace__slug=workspace_slug,
|
||||
member__is_bot=False,
|
||||
).count(),
|
||||
)
|
||||
# Update the last synced time
|
||||
workspace_license.last_synced_at = timezone.now()
|
||||
workspace_license.is_cancelled = response.get(
|
||||
"is_cancelled", False
|
||||
)
|
||||
workspace_license.free_seats = response.get("free_seats", 12)
|
||||
workspace_license.purchased_seats = response.get(
|
||||
"purchased_seats", 0
|
||||
)
|
||||
workspace_license.current_period_end_date = response.get(
|
||||
"current_period_end_date"
|
||||
)
|
||||
workspace_license.recurring_interval = response.get("interval")
|
||||
workspace_license.plan = response.get("plan")
|
||||
workspace_license.is_offline_payment = response.get(
|
||||
"is_offline_payment", False
|
||||
)
|
||||
workspace_license.trial_end_date = response.get("trial_end_date")
|
||||
workspace_license.has_activated_free_trial = response.get(
|
||||
"has_activated_free_trial", False
|
||||
)
|
||||
workspace_license.has_added_payment_method = response.get(
|
||||
"has_added_payment_method", False
|
||||
)
|
||||
workspace_license.subscription = response.get("subscription")
|
||||
workspace_license.save()
|
||||
return {
|
||||
"is_cancelled": workspace_license.is_cancelled,
|
||||
"purchased_seats": workspace_license.purchased_seats,
|
||||
"current_period_end_date": workspace_license.current_period_end_date,
|
||||
"interval": workspace_license.recurring_interval,
|
||||
"product": workspace_license.plan,
|
||||
"is_offline_payment": workspace_license.is_offline_payment,
|
||||
"trial_end_date": workspace_license.trial_end_date,
|
||||
"has_activated_free_trial": workspace_license.has_activated_free_trial,
|
||||
"has_added_payment_method": workspace_license.has_added_payment_method,
|
||||
"subscription": workspace_license.subscription,
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"is_cancelled": workspace_license.is_cancelled,
|
||||
"purchased_seats": workspace_license.purchased_seats,
|
||||
"current_period_end_date": workspace_license.current_period_end_date,
|
||||
"interval": workspace_license.recurring_interval,
|
||||
"product": workspace_license.plan,
|
||||
"is_offline_payment": workspace_license.is_offline_payment,
|
||||
"trial_end_date": workspace_license.trial_end_date,
|
||||
"has_activated_free_trial": workspace_license.has_activated_free_trial,
|
||||
"has_added_payment_method": workspace_license.has_added_payment_method,
|
||||
"subscription": workspace_license.subscription,
|
||||
}
|
||||
# If the license is not present, then fetch the license from the payment server and create it
|
||||
else:
|
||||
# Fetch the workspace license
|
||||
response = fetch_workspace_license(
|
||||
workspace_id=str(workspace.id),
|
||||
workspace_slug=workspace_slug,
|
||||
free_seats=WorkspaceMember.objects.filter(
|
||||
is_active=True,
|
||||
workspace__slug=workspace_slug,
|
||||
member__is_bot=False,
|
||||
).count(),
|
||||
)
|
||||
# Create the workspace license
|
||||
workspace_license = WorkspaceLicense.objects.create(
|
||||
workspace=workspace,
|
||||
is_cancelled=response.get("is_cancelled", False),
|
||||
purchased_seats=response.get("purchased_seats", 0),
|
||||
free_seats=response.get("free_seats", 12),
|
||||
current_period_end_date=response.get("current_period_end_date"),
|
||||
recurring_interval=response.get("interval"),
|
||||
plan=response.get("plan"),
|
||||
last_synced_at=timezone.now(),
|
||||
trial_end_date=response.get("trial_end_date"),
|
||||
has_activated_free_trial=response.get(
|
||||
"has_activated_free_trial", False
|
||||
),
|
||||
has_added_payment_method=response.get(
|
||||
"has_added_payment_method", False
|
||||
),
|
||||
subscription=response.get("subscription"),
|
||||
)
|
||||
# Return the workspace license
|
||||
return {
|
||||
"is_cancelled": workspace_license.is_cancelled,
|
||||
"purchased_seats": workspace_license.purchased_seats,
|
||||
"current_period_end_date": workspace_license.current_period_end_date,
|
||||
"interval": workspace_license.recurring_interval,
|
||||
"product": workspace_license.plan,
|
||||
"is_offline_payment": workspace_license.is_offline_payment,
|
||||
"trial_end_date": workspace_license.trial_end_date,
|
||||
"has_activated_free_trial": workspace_license.has_activated_free_trial,
|
||||
"has_added_payment_method": workspace_license.has_added_payment_method,
|
||||
"subscription": workspace_license.subscription,
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ import requests
|
||||
|
||||
# Django imports
|
||||
from django.conf import settings
|
||||
from django.db.models import CharField
|
||||
from django.db.models.functions import Cast
|
||||
from django.utils import timezone
|
||||
from django.db.models import F
|
||||
|
||||
@@ -18,11 +16,11 @@ from .base import BaseAPIView
|
||||
from plane.app.permissions.workspace import (
|
||||
WorkspaceUserPermission,
|
||||
)
|
||||
from plane.db.models import WorkspaceMember, Workspace
|
||||
from plane.db.models import WorkspaceMember
|
||||
from plane.ee.models import WorkspaceLicense
|
||||
from plane.utils.exception_logger import log_exception
|
||||
from plane.payment.utils.workspace_license_request import (
|
||||
fetch_workspace_license,
|
||||
resync_workspace_license,
|
||||
)
|
||||
|
||||
|
||||
@@ -119,6 +117,7 @@ class WebsiteUserWorkspaceEndpoint(BaseAPIView):
|
||||
"has_added_payment_method",
|
||||
"current_period_end_date",
|
||||
"is_offline_payment",
|
||||
"subscription",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -149,137 +148,9 @@ class WorkspaceProductEndpoint(BaseAPIView):
|
||||
def get(self, request, slug):
|
||||
try:
|
||||
if settings.PAYMENT_SERVER_BASE_URL:
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
|
||||
# Check if the license is present for the workspace
|
||||
workspace_license = WorkspaceLicense.objects.filter(
|
||||
workspace=workspace
|
||||
).first()
|
||||
|
||||
# If the license is present, then check if the last sync is more than 1 hour
|
||||
if workspace_license:
|
||||
# If the last sync is more than 1 hour, then sync the license
|
||||
if (
|
||||
workspace_license.last_synced_at - timezone.now()
|
||||
).seconds > 3600:
|
||||
response = fetch_workspace_license(
|
||||
workspace_id=str(workspace.id),
|
||||
workspace_slug=slug,
|
||||
free_seats=WorkspaceMember.objects.filter(
|
||||
is_active=True,
|
||||
workspace__slug=slug,
|
||||
member__is_bot=False,
|
||||
).count(),
|
||||
)
|
||||
|
||||
# Update the last synced time
|
||||
workspace_license.last_synced_at = timezone.now()
|
||||
workspace_license.is_cancelled = response.get(
|
||||
"is_cancelled", False
|
||||
)
|
||||
workspace_license.free_seats = response.get(
|
||||
"free_seats", 12
|
||||
)
|
||||
workspace_license.purchased_seats = response.get(
|
||||
"purchased_seats", 0
|
||||
)
|
||||
workspace_license.current_period_end_date = (
|
||||
response.get("current_period_end_date")
|
||||
)
|
||||
workspace_license.recurring_interval = response.get(
|
||||
"interval"
|
||||
)
|
||||
workspace_license.plan = response.get("plan")
|
||||
workspace_license.is_offline_payment = response.get(
|
||||
"is_offline_payment", False
|
||||
)
|
||||
workspace_license.trial_end_date = response.get(
|
||||
"trial_end_date"
|
||||
)
|
||||
workspace_license.has_activated_free_trial = (
|
||||
response.get("has_activated_free_trial", False)
|
||||
)
|
||||
workspace_license.has_added_payment_method = (
|
||||
response.get("has_added_payment_method", False)
|
||||
)
|
||||
workspace_license.save()
|
||||
|
||||
return Response(
|
||||
{
|
||||
"is_cancelled": workspace_license.is_cancelled,
|
||||
"purchased_seats": workspace_license.purchased_seats,
|
||||
"current_period_end_date": workspace_license.current_period_end_date,
|
||||
"interval": workspace_license.recurring_interval,
|
||||
"product": workspace_license.plan,
|
||||
"is_offline_payment": workspace_license.is_offline_payment,
|
||||
"trial_end_date": workspace_license.trial_end_date,
|
||||
"has_activated_free_trial": workspace_license.has_activated_free_trial,
|
||||
"has_added_payment_method": workspace_license.has_added_payment_method,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
else:
|
||||
return Response(
|
||||
{
|
||||
"is_cancelled": workspace_license.is_cancelled,
|
||||
"purchased_seats": workspace_license.purchased_seats,
|
||||
"current_period_end_date": workspace_license.current_period_end_date,
|
||||
"interval": workspace_license.recurring_interval,
|
||||
"product": workspace_license.plan,
|
||||
"is_offline_payment": workspace_license.is_offline_payment,
|
||||
"trial_end_date": workspace_license.trial_end_date,
|
||||
"has_activated_free_trial": workspace_license.has_activated_free_trial,
|
||||
"has_added_payment_method": workspace_license.has_added_payment_method,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
# If the license is not present, then fetch the license from the payment server and create it
|
||||
else:
|
||||
# Fetch the workspace license
|
||||
response = fetch_workspace_license(
|
||||
workspace_id=str(workspace.id),
|
||||
workspace_slug=slug,
|
||||
free_seats=WorkspaceMember.objects.filter(
|
||||
is_active=True,
|
||||
workspace__slug=slug,
|
||||
member__is_bot=False,
|
||||
).count(),
|
||||
)
|
||||
# Create the workspace license
|
||||
workspace_license = WorkspaceLicense.objects.create(
|
||||
workspace=workspace,
|
||||
is_cancelled=response.get("is_cancelled", False),
|
||||
purchased_seats=response.get("purchased_seats", 0),
|
||||
free_seats=response.get("free_seats", 12),
|
||||
current_period_end_date=response.get(
|
||||
"current_period_end_date"
|
||||
),
|
||||
recurring_interval=response.get("interval"),
|
||||
plan=response.get("plan"),
|
||||
last_synced_at=timezone.now(),
|
||||
trial_end_date=response.get("trial_end_date"),
|
||||
has_activated_free_trial=response.get(
|
||||
"has_activated_free_trial", False
|
||||
),
|
||||
has_added_payment_method=response.get(
|
||||
"has_added_payment_method", False
|
||||
),
|
||||
)
|
||||
# Return the workspace license
|
||||
return Response(
|
||||
{
|
||||
"is_cancelled": workspace_license.is_cancelled,
|
||||
"purchased_seats": workspace_license.purchased_seats,
|
||||
"current_period_end_date": workspace_license.current_period_end_date,
|
||||
"interval": workspace_license.recurring_interval,
|
||||
"product": workspace_license.plan,
|
||||
"is_offline_payment": workspace_license.is_offline_payment,
|
||||
"trial_end_date": workspace_license.trial_end_date,
|
||||
"has_activated_free_trial": workspace_license.has_activated_free_trial,
|
||||
"has_added_payment_method": workspace_license.has_added_payment_method,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
# Resync the workspace license
|
||||
response = resync_workspace_license(workspace_slug=slug)
|
||||
return Response(response, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response(
|
||||
{"error": "error fetching product details"},
|
||||
@@ -295,84 +166,16 @@ class WorkspaceProductEndpoint(BaseAPIView):
|
||||
|
||||
class WorkspaceLicenseRefreshEndpoint(BaseAPIView):
|
||||
def post(self, request, slug):
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
|
||||
# Check if the license is present for the workspace
|
||||
workspace_license = WorkspaceLicense.objects.filter(
|
||||
workspace=workspace
|
||||
).first()
|
||||
|
||||
# If the license is present, then fetch the license from the payment server and update it
|
||||
if workspace_license:
|
||||
# Update the values in the workspace license
|
||||
response = fetch_workspace_license(
|
||||
workspace_id=str(workspace.id),
|
||||
workspace_slug=slug,
|
||||
free_seats=WorkspaceMember.objects.filter(
|
||||
is_active=True,
|
||||
workspace__slug=slug,
|
||||
member__is_bot=False,
|
||||
).count(),
|
||||
)
|
||||
workspace_license.is_cancelled = response.get(
|
||||
"is_cancelled", False
|
||||
)
|
||||
workspace_license.free_seats = response.get("free_seats", 12)
|
||||
workspace_license.purchased_seats = response.get(
|
||||
"purchased_seats", 0
|
||||
)
|
||||
workspace_license.current_period_end_date = response.get(
|
||||
"current_period_end_date"
|
||||
)
|
||||
workspace_license.recurring_interval = response.get("interval")
|
||||
workspace_license.plan = response.get("plan")
|
||||
workspace_license.trial_end_date = response.get("trial_end_date")
|
||||
workspace_license.has_activated_free_trial = response.get(
|
||||
"has_activated_free_trial", False
|
||||
)
|
||||
workspace_license.has_added_payment_method = response.get(
|
||||
"has_added_payment_method", False
|
||||
)
|
||||
workspace_license.last_synced_at = timezone.now()
|
||||
workspace_license.save()
|
||||
# If the license is not present, then fetch the license from the payment server and create it
|
||||
else:
|
||||
# Fetch the workspace license
|
||||
response = fetch_workspace_license(
|
||||
workspace_id=str(workspace.id),
|
||||
workspace_slug=slug,
|
||||
free_seats=WorkspaceMember.objects.filter(
|
||||
is_active=True,
|
||||
workspace__slug=slug,
|
||||
member__is_bot=False,
|
||||
).count(),
|
||||
)
|
||||
# Create the workspace license
|
||||
workspace_license = WorkspaceLicense.objects.create(
|
||||
workspace=workspace,
|
||||
is_cancelled=response.get("is_cancelled", False),
|
||||
purchased_seats=response.get("purchased_seats", 0),
|
||||
free_seats=response.get("free_seats", 12),
|
||||
current_period_end_date=response.get(
|
||||
"current_period_end_date"
|
||||
),
|
||||
recurring_interval=response.get("interval"),
|
||||
plan=response.get("plan"),
|
||||
last_synced_at=timezone.now(),
|
||||
trial_end_date=response.get("trial_end_date"),
|
||||
has_activated_free_trial=response.get(
|
||||
"has_activated_free_trial", False
|
||||
),
|
||||
has_added_payment_method=response.get(
|
||||
"has_added_payment_method", False
|
||||
),
|
||||
)
|
||||
# Resync the workspace license
|
||||
_ = resync_workspace_license(workspace_slug=slug, force=True)
|
||||
|
||||
# Return the response
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class WorkspaceLicenseSyncEndpoint(BaseAPIView):
|
||||
"""This endpoint is used to sync the workspace license from the payment server"""
|
||||
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
@@ -427,6 +230,7 @@ class WorkspaceLicenseSyncEndpoint(BaseAPIView):
|
||||
workspace_license.has_added_payment_method = request.data.get(
|
||||
"has_added_payment_method", False
|
||||
)
|
||||
workspace_license.subscription = request.data.get("subscription")
|
||||
workspace_license.save()
|
||||
# If the workspace license is not present, then fetch the license from the payment server and create it
|
||||
else:
|
||||
@@ -449,6 +253,7 @@ class WorkspaceLicenseSyncEndpoint(BaseAPIView):
|
||||
has_added_payment_method=request.data.get(
|
||||
"has_added_payment_method", False
|
||||
),
|
||||
subscription=request.data.get("subscription"),
|
||||
)
|
||||
|
||||
# Return the response
|
||||
|
||||
@@ -3,7 +3,6 @@ import requests
|
||||
|
||||
# Django imports
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
# Third party imports
|
||||
from rest_framework import status
|
||||
@@ -12,11 +11,10 @@ from rest_framework.response import Response
|
||||
# Module imports
|
||||
from .base import BaseAPIView
|
||||
from plane.app.permissions.workspace import WorkspaceOwnerPermission
|
||||
from plane.db.models import Workspace, WorkspaceMember
|
||||
from plane.ee.models import WorkspaceLicense
|
||||
from plane.db.models import Workspace
|
||||
from plane.utils.exception_logger import log_exception
|
||||
from plane.payment.utils.workspace_license_request import (
|
||||
fetch_workspace_license,
|
||||
resync_workspace_license,
|
||||
)
|
||||
|
||||
|
||||
@@ -102,90 +100,7 @@ class UpgradeSubscriptionEndpoint(BaseAPIView):
|
||||
response.raise_for_status()
|
||||
|
||||
# Refetch the workspace license
|
||||
workspace_license = WorkspaceLicense.objects.filter(
|
||||
workspace=workspace
|
||||
).first()
|
||||
|
||||
# Refetch the workspace license
|
||||
workspace_license_response = fetch_workspace_license(
|
||||
workspace_id=str(workspace.id),
|
||||
workspace_slug=slug,
|
||||
free_seats=WorkspaceMember.objects.filter(
|
||||
is_active=True,
|
||||
workspace__slug=slug,
|
||||
member__is_bot=False,
|
||||
).count(),
|
||||
)
|
||||
|
||||
if workspace_license:
|
||||
# Update the last synced time
|
||||
workspace_license.last_synced_at = timezone.now()
|
||||
workspace_license.is_cancelled = (
|
||||
workspace_license_response.get("is_cancelled", False)
|
||||
)
|
||||
workspace_license.purchased_seats = (
|
||||
workspace_license_response.get("purchased_seats", 0)
|
||||
)
|
||||
workspace_license.free_seats = (
|
||||
workspace_license_response.get("free_seats", 12)
|
||||
)
|
||||
workspace_license.current_period_end_date = (
|
||||
workspace_license_response.get(
|
||||
"current_period_end_date"
|
||||
)
|
||||
)
|
||||
workspace_license.recurring_interval = (
|
||||
workspace_license_response.get("interval")
|
||||
)
|
||||
workspace_license.plan = workspace_license_response.get(
|
||||
"plan"
|
||||
)
|
||||
workspace_license.trial_end_date = (
|
||||
workspace_license_response.get("trial_end_date")
|
||||
)
|
||||
workspace_license.has_activated_free_trial = (
|
||||
workspace_license_response.get(
|
||||
"has_activated_free_trial"
|
||||
)
|
||||
)
|
||||
workspace_license.has_added_payment_method = (
|
||||
workspace_license_response.get(
|
||||
"has_added_payment_method"
|
||||
)
|
||||
)
|
||||
workspace_license.save()
|
||||
else:
|
||||
# Create a new workspace license
|
||||
WorkspaceLicense.objects.create(
|
||||
workspace=workspace,
|
||||
last_synced_at=timezone.now(),
|
||||
is_cancelled=workspace_license_response.get(
|
||||
"is_cancelled", False
|
||||
),
|
||||
purchased_seats=workspace_license_response.get(
|
||||
"purchased_seats", 0
|
||||
),
|
||||
free_seats=workspace_license_response.get(
|
||||
"free_seats", 12
|
||||
),
|
||||
current_period_end_date=workspace_license_response.get(
|
||||
"current_period_end_date"
|
||||
),
|
||||
recurring_interval=workspace_license_response.get(
|
||||
"interval"
|
||||
),
|
||||
trial_end_date=workspace_license_response.get(
|
||||
"trial_end_date"
|
||||
),
|
||||
plan=workspace_license_response.get("plan"),
|
||||
has_activated_free_trial=workspace_license_response.get(
|
||||
"has_activated_free_trial"
|
||||
),
|
||||
has_added_payment_method=workspace_license_response.get(
|
||||
"has_added_payment_method"
|
||||
),
|
||||
)
|
||||
|
||||
resync_workspace_license(workspace_slug=workspace.slug)
|
||||
# Return the response
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
return Response(
|
||||
|
||||
1
packages/types/src/payment.d.ts
vendored
1
packages/types/src/payment.d.ts
vendored
@@ -27,4 +27,5 @@ export type IWorkspaceProductSubscription = {
|
||||
purchased_seats: number | undefined;
|
||||
has_activated_free_trial: boolean;
|
||||
has_added_payment_method: boolean;
|
||||
subscription: string | undefined;
|
||||
};
|
||||
|
||||
@@ -48,7 +48,7 @@ const CloudUpgradePlanPage = observer(() => {
|
||||
|
||||
// fetch products
|
||||
const { data: products, isLoading: isLoadingProduct } = useSWR(
|
||||
workspaceSlug ? "CLOUD_PRO_PRODUCTS" : null,
|
||||
workspaceSlug ? `CLOUD_PRO_PRODUCTS_${workspaceSlug?.toString()}` : null,
|
||||
workspaceSlug ? () => paymentService.listProducts(workspaceSlug.toString()) : null,
|
||||
{
|
||||
errorRetryCount: 2,
|
||||
|
||||
@@ -72,6 +72,7 @@ const CloudUpgradePage = observer(() => {
|
||||
};
|
||||
|
||||
const workspaceSubscription = (workspace: TWorkspaceWithProductDetails): "FREE" | "PRO" | "TRIAL" => {
|
||||
if (!workspace.subscription) return "FREE";
|
||||
switch (workspace.product) {
|
||||
case "PRO":
|
||||
if (workspace.is_offline_payment) {
|
||||
|
||||
@@ -98,7 +98,7 @@ export const ProjectFeaturesList: FC<Props> = observer((props) => {
|
||||
</div>
|
||||
|
||||
<ToggleSwitch
|
||||
value={Boolean(currentProjectDetails?.[featureItem.property as keyof IProject])}
|
||||
value={Boolean(isWorklogEnabled && currentProjectDetails?.[featureItem.property as keyof IProject])}
|
||||
onChange={() => handleSubmit(featureItemKey, featureItem.property)}
|
||||
disabled={
|
||||
!featureItem.isEnabled || !isAdmin || featureItem.property === "is_time_tracking_enabled"
|
||||
|
||||
@@ -67,13 +67,10 @@ export const CloudEditionBadge = observer(() => {
|
||||
</Loader>
|
||||
);
|
||||
|
||||
// derived values
|
||||
const isInTrialPeriod =
|
||||
subscriptionDetail?.has_activated_free_trial && subscriptionDetail?.trial_end_date ? true : false;
|
||||
const isTrialCompleted =
|
||||
subscriptionDetail?.has_activated_free_trial && !subscriptionDetail?.trial_end_date ? true : false;
|
||||
|
||||
const renderButtonText = () => {
|
||||
if (!subscriptionDetail.subscription) {
|
||||
return "Upgrade to Pro";
|
||||
}
|
||||
switch (subscriptionDetail.product) {
|
||||
case "FREE": {
|
||||
if (subscriptionDetail?.has_activated_free_trial) {
|
||||
@@ -98,6 +95,9 @@ export const CloudEditionBadge = observer(() => {
|
||||
};
|
||||
|
||||
const showPaymentButton = () => {
|
||||
if (!subscriptionDetail.subscription) {
|
||||
return true;
|
||||
}
|
||||
switch (subscriptionDetail.product) {
|
||||
case "FREE": {
|
||||
return true;
|
||||
@@ -134,7 +134,6 @@ export const CloudEditionBadge = observer(() => {
|
||||
handleClose={() => toggleProPlanModal(false)}
|
||||
yearlyPlan={false}
|
||||
handleSuccessModal={() => setProPlanSuccessModalOpen(true)}
|
||||
canFetchProducts={subscriptionDetail.product === "FREE" || isInTrialPeriod || isTrialCompleted}
|
||||
/>
|
||||
|
||||
{showPaymentButton() && (
|
||||
|
||||
@@ -26,6 +26,9 @@ export const PlaneCloudBilling: React.FC = observer(() => {
|
||||
// derived values
|
||||
const endDate = currentWorkspaceSubscribedPlanDetail?.current_period_end_date;
|
||||
const isSubscriptionCancelled = currentWorkspaceSubscribedPlanDetail?.is_canceled;
|
||||
const isInTrialPeriod =
|
||||
!currentWorkspaceSubscribedPlanDetail?.has_added_payment_method &&
|
||||
!!currentWorkspaceSubscribedPlanDetail?.trial_end_date;
|
||||
|
||||
const handleSubscriptionPageRedirection = () => {
|
||||
setIsLoading(true);
|
||||
@@ -102,15 +105,26 @@ export const PlaneCloudBilling: React.FC = observer(() => {
|
||||
<div className="flex items-center gap-2">
|
||||
<Image src={PlaneLogo} alt="Plane pro" width={24} height={24} />
|
||||
<h4 className="text-2xl mb-1 leading-6 font-bold">Plane Pro</h4>
|
||||
{isSubscriptionCancelled ? (
|
||||
<div className="text-center text-sm text-red-500 font-medium">
|
||||
(Expires on: {renderFormattedDate(endDate)})
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center text-sm text-custom-text-200 font-medium">
|
||||
(Renew on: {renderFormattedDate(endDate)})
|
||||
</div>
|
||||
{isInTrialPeriod && (
|
||||
<>
|
||||
<div className="flex-shrink-0 p-1 px-2 uppercase bg-custom-primary-100/20 text-custom-primary-100 text-xs rounded-full font-medium">
|
||||
Free Trial
|
||||
</div>
|
||||
<div className="text-center text-sm text-custom-text-200 font-medium">
|
||||
(Free trial ends on: {renderFormattedDate(currentWorkspaceSubscribedPlanDetail?.trial_end_date)})
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!isInTrialPeriod &&
|
||||
(isSubscriptionCancelled ? (
|
||||
<div className="text-center text-sm text-red-500 font-medium">
|
||||
(Expires on: {renderFormattedDate(endDate)})
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center text-sm text-custom-text-200 font-medium">
|
||||
(Renew on: {renderFormattedDate(endDate)})
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{!currentWorkspaceSubscribedPlanDetail.is_offline_payment && (
|
||||
<div>
|
||||
|
||||
@@ -57,7 +57,7 @@ export const ProPlanCloudUpgradeModal: FC<ProPlanCloudUpgradeModalProps> = (prop
|
||||
const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
|
||||
// fetch products
|
||||
const { isLoading: isProductsAPILoading, data } = useSWR(
|
||||
workspaceSlug && canFetchProducts ? "CLOUD_PAYMENT_PRODUCTS" : null,
|
||||
workspaceSlug && canFetchProducts ? `CLOUD_PAYMENT_PRODUCTS_${workspaceSlug?.toString()}` : null,
|
||||
workspaceSlug && canFetchProducts ? () => paymentService.listProducts(workspaceSlug.toString()) : null,
|
||||
{
|
||||
errorRetryCount: 2,
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
import { useParams } from "next/navigation";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
// types
|
||||
import { IProjectView, IWorkspaceView } from "@plane/types";
|
||||
// components
|
||||
import { AccessField } from "@/components/common/access-field";
|
||||
// constants
|
||||
import { VIEW_ACCESS_SPECIFIERS } from "@/constants/views";
|
||||
import { useFlag } from "@/plane-web/hooks/store";
|
||||
|
||||
type Props = {
|
||||
control: Control<IProjectView, any> | Control<IWorkspaceView, any>;
|
||||
};
|
||||
export const AccessController = (props: Props) => {
|
||||
const { control } = props;
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
// plane web hooks
|
||||
const isPrivateViewsEnabled = useFlag(workspaceSlug?.toString(), "VIEW_ACCESS_PRIVATE");
|
||||
|
||||
if (!isPrivateViewsEnabled) return null;
|
||||
|
||||
return (
|
||||
<Controller
|
||||
control={control as Control<IProjectView, any>}
|
||||
|
||||
@@ -63,6 +63,7 @@ export class WorkspaceSubscriptionStore implements IWorkspaceSubscriptionStore {
|
||||
purchased_seats: response?.purchased_seats || 0,
|
||||
has_activated_free_trial: response?.has_activated_free_trial || false,
|
||||
has_added_payment_method: response?.has_added_payment_method || false,
|
||||
subscription: response?.subscription || undefined,
|
||||
});
|
||||
});
|
||||
return response;
|
||||
|
||||
1
web/ee/types/workspace.d.ts
vendored
1
web/ee/types/workspace.d.ts
vendored
@@ -12,4 +12,5 @@ export type TWorkspaceWithProductDetails = {
|
||||
has_added_payment_method: boolean;
|
||||
trial_end_date: string;
|
||||
is_offline_payment: boolean;
|
||||
subscription: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user