mirror of https://github.com/zulip/zulip.git
support: Add admin support for updating end date of active plan.
This currently will only apply to tier.SELF_HOSTED_LEGACY plans.
This commit is contained in:
parent
71263ac2ab
commit
2994685399
|
@ -336,6 +336,44 @@ class TestRemoteServerSupportEndpoint(ZulipTestCase):
|
||||||
check_no_sponsorship_request(result)
|
check_no_sponsorship_request(result)
|
||||||
check_legacy_plan_without_upgrade(result)
|
check_legacy_plan_without_upgrade(result)
|
||||||
|
|
||||||
|
def test_extend_current_plan_end_date(self) -> None:
|
||||||
|
remote_realm = RemoteRealm.objects.get(name="realm-name-5")
|
||||||
|
customer = Customer.objects.get(remote_realm=remote_realm)
|
||||||
|
plan = get_current_plan_by_customer(customer)
|
||||||
|
assert plan is not None
|
||||||
|
self.assertEqual(plan.status, CustomerPlan.ACTIVE)
|
||||||
|
self.assertEqual(plan.end_date, datetime(2050, 2, 1, tzinfo=timezone.utc))
|
||||||
|
|
||||||
|
cordelia = self.example_user("cordelia")
|
||||||
|
self.login_user(cordelia)
|
||||||
|
result = self.client_post(
|
||||||
|
"/activity/remote/support",
|
||||||
|
{"realm_id": f"{remote_realm.id}", "end_date": "2040-01-01"},
|
||||||
|
)
|
||||||
|
self.assertEqual(result.status_code, 302)
|
||||||
|
self.assertEqual(result["Location"], "/login/")
|
||||||
|
|
||||||
|
iago = self.example_user("iago")
|
||||||
|
self.login_user(iago)
|
||||||
|
|
||||||
|
result = self.client_post(
|
||||||
|
"/activity/remote/support",
|
||||||
|
{"remote_realm_id": f"{remote_realm.id}", "plan_end_date": "2040-01-01"},
|
||||||
|
)
|
||||||
|
self.assert_in_success_response(
|
||||||
|
["Current plan for realm-name-5 updated to end on 2040-01-01."], result
|
||||||
|
)
|
||||||
|
plan.refresh_from_db()
|
||||||
|
self.assertEqual(plan.end_date, datetime(2040, 1, 1, tzinfo=timezone.utc))
|
||||||
|
|
||||||
|
result = self.client_post(
|
||||||
|
"/activity/remote/support",
|
||||||
|
{"remote_realm_id": f"{remote_realm.id}", "plan_end_date": "2020-01-01"},
|
||||||
|
)
|
||||||
|
self.assert_in_success_response(
|
||||||
|
["Cannot update current plan for realm-name-5 to end on 2020-01-01."], result
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestSupportEndpoint(ZulipTestCase):
|
class TestSupportEndpoint(ZulipTestCase):
|
||||||
def create_customer_and_plan(self, realm: Realm, monthly: bool = False) -> Customer:
|
def create_customer_and_plan(self, realm: Realm, monthly: bool = False) -> Customer:
|
||||||
|
|
|
@ -33,7 +33,13 @@ from zerver.lib.exceptions import JsonableError
|
||||||
from zerver.lib.realm_icon import realm_icon_url
|
from zerver.lib.realm_icon import realm_icon_url
|
||||||
from zerver.lib.request import REQ, has_request_variables
|
from zerver.lib.request import REQ, has_request_variables
|
||||||
from zerver.lib.subdomains import get_subdomain_from_hostname
|
from zerver.lib.subdomains import get_subdomain_from_hostname
|
||||||
from zerver.lib.validator import check_bool, check_string_in, to_decimal, to_non_negative_int
|
from zerver.lib.validator import (
|
||||||
|
check_bool,
|
||||||
|
check_date,
|
||||||
|
check_string_in,
|
||||||
|
to_decimal,
|
||||||
|
to_non_negative_int,
|
||||||
|
)
|
||||||
from zerver.models import (
|
from zerver.models import (
|
||||||
MultiuseInvite,
|
MultiuseInvite,
|
||||||
PreregistrationRealm,
|
PreregistrationRealm,
|
||||||
|
@ -416,6 +422,7 @@ def remote_servers_support(
|
||||||
billing_modality: Optional[str] = REQ(
|
billing_modality: Optional[str] = REQ(
|
||||||
default=None, str_validator=check_string_in(VALID_BILLING_MODALITY_VALUES)
|
default=None, str_validator=check_string_in(VALID_BILLING_MODALITY_VALUES)
|
||||||
),
|
),
|
||||||
|
plan_end_date: Optional[str] = REQ(default=None, str_validator=check_date),
|
||||||
modify_plan: Optional[str] = REQ(
|
modify_plan: Optional[str] = REQ(
|
||||||
default=None, str_validator=check_string_in(VALID_MODIFY_PLAN_METHODS)
|
default=None, str_validator=check_string_in(VALID_MODIFY_PLAN_METHODS)
|
||||||
),
|
),
|
||||||
|
@ -470,6 +477,11 @@ def remote_servers_support(
|
||||||
support_type=SupportType.update_billing_modality,
|
support_type=SupportType.update_billing_modality,
|
||||||
billing_modality=billing_modality,
|
billing_modality=billing_modality,
|
||||||
)
|
)
|
||||||
|
elif plan_end_date is not None:
|
||||||
|
support_view_request = SupportViewRequest(
|
||||||
|
support_type=SupportType.update_plan_end_date,
|
||||||
|
plan_end_date=plan_end_date,
|
||||||
|
)
|
||||||
elif modify_plan is not None:
|
elif modify_plan is not None:
|
||||||
support_view_request = SupportViewRequest(
|
support_view_request = SupportViewRequest(
|
||||||
support_type=SupportType.modify_plan,
|
support_type=SupportType.modify_plan,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import os
|
||||||
import secrets
|
import secrets
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
@ -554,6 +554,7 @@ class SupportType(Enum):
|
||||||
update_billing_modality = 4
|
update_billing_modality = 4
|
||||||
modify_plan = 5
|
modify_plan = 5
|
||||||
update_minimum_licenses = 6
|
update_minimum_licenses = 6
|
||||||
|
update_plan_end_date = 7
|
||||||
|
|
||||||
|
|
||||||
class SupportViewRequest(TypedDict, total=False):
|
class SupportViewRequest(TypedDict, total=False):
|
||||||
|
@ -564,6 +565,7 @@ class SupportViewRequest(TypedDict, total=False):
|
||||||
plan_modification: Optional[str]
|
plan_modification: Optional[str]
|
||||||
new_plan_tier: Optional[int]
|
new_plan_tier: Optional[int]
|
||||||
minimum_licenses: Optional[int]
|
minimum_licenses: Optional[int]
|
||||||
|
plan_end_date: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
class AuditLogEventType(Enum):
|
class AuditLogEventType(Enum):
|
||||||
|
@ -578,6 +580,7 @@ class AuditLogEventType(Enum):
|
||||||
CUSTOMER_SWITCHED_FROM_ANNUAL_TO_MONTHLY_PLAN = 9
|
CUSTOMER_SWITCHED_FROM_ANNUAL_TO_MONTHLY_PLAN = 9
|
||||||
BILLING_ENTITY_PLAN_TYPE_CHANGED = 10
|
BILLING_ENTITY_PLAN_TYPE_CHANGED = 10
|
||||||
MINIMUM_LICENSES_CHANGED = 11
|
MINIMUM_LICENSES_CHANGED = 11
|
||||||
|
CUSTOMER_PLAN_END_DATE_CHANGED = 12
|
||||||
|
|
||||||
|
|
||||||
class PlanTierChangeType(Enum):
|
class PlanTierChangeType(Enum):
|
||||||
|
@ -1166,6 +1169,31 @@ class BillingSession(ABC):
|
||||||
success_message = f"Billing collection method of {self.billing_entity_display_name} updated to send invoice."
|
success_message = f"Billing collection method of {self.billing_entity_display_name} updated to send invoice."
|
||||||
return success_message
|
return success_message
|
||||||
|
|
||||||
|
def update_end_date_of_current_plan(self, end_date_string: str) -> str:
|
||||||
|
new_end_date = datetime.strptime(end_date_string, "%Y-%m-%d").replace(tzinfo=timezone.utc)
|
||||||
|
if new_end_date.date() <= timezone_now().date():
|
||||||
|
raise SupportRequestError(
|
||||||
|
f"Cannot update current plan for {self.billing_entity_display_name} to end on {end_date_string}."
|
||||||
|
)
|
||||||
|
customer = self.get_customer()
|
||||||
|
if customer is not None:
|
||||||
|
plan = get_current_plan_by_customer(customer)
|
||||||
|
if plan is not None:
|
||||||
|
assert plan.end_date is not None
|
||||||
|
assert plan.status == CustomerPlan.ACTIVE
|
||||||
|
old_end_date = plan.end_date.strftime("%Y-%m-%d")
|
||||||
|
plan.end_date = new_end_date
|
||||||
|
plan.save(update_fields=["end_date"])
|
||||||
|
self.write_to_audit_log(
|
||||||
|
event_type=AuditLogEventType.CUSTOMER_PLAN_END_DATE_CHANGED,
|
||||||
|
event_time=timezone_now(),
|
||||||
|
extra_data={"old_end_date": old_end_date, "new_end_date": end_date_string},
|
||||||
|
)
|
||||||
|
return f"Current plan for {self.billing_entity_display_name} updated to end on {end_date_string}."
|
||||||
|
raise SupportRequestError(
|
||||||
|
f"No current plan for {self.billing_entity_display_name}."
|
||||||
|
) # nocoverage
|
||||||
|
|
||||||
def setup_upgrade_payment_intent_and_charge(
|
def setup_upgrade_payment_intent_and_charge(
|
||||||
self,
|
self,
|
||||||
plan_tier: int,
|
plan_tier: int,
|
||||||
|
@ -2661,6 +2689,10 @@ class BillingSession(ABC):
|
||||||
assert support_request["billing_modality"] in VALID_BILLING_MODALITY_VALUES
|
assert support_request["billing_modality"] in VALID_BILLING_MODALITY_VALUES
|
||||||
charge_automatically = support_request["billing_modality"] == "charge_automatically"
|
charge_automatically = support_request["billing_modality"] == "charge_automatically"
|
||||||
success_message = self.update_billing_modality_of_current_plan(charge_automatically)
|
success_message = self.update_billing_modality_of_current_plan(charge_automatically)
|
||||||
|
elif support_type == SupportType.update_plan_end_date:
|
||||||
|
assert support_request["plan_end_date"] is not None
|
||||||
|
new_plan_end_date = support_request["plan_end_date"]
|
||||||
|
success_message = self.update_end_date_of_current_plan(new_plan_end_date)
|
||||||
elif support_type == SupportType.modify_plan:
|
elif support_type == SupportType.modify_plan:
|
||||||
assert support_request["plan_modification"] is not None
|
assert support_request["plan_modification"] is not None
|
||||||
plan_modification = support_request["plan_modification"]
|
plan_modification = support_request["plan_modification"]
|
||||||
|
@ -2986,6 +3018,8 @@ class RealmBillingSession(BillingSession):
|
||||||
return RealmAuditLog.REALM_SPONSORSHIP_PENDING_STATUS_CHANGED
|
return RealmAuditLog.REALM_SPONSORSHIP_PENDING_STATUS_CHANGED
|
||||||
elif event_type is AuditLogEventType.BILLING_MODALITY_CHANGED:
|
elif event_type is AuditLogEventType.BILLING_MODALITY_CHANGED:
|
||||||
return RealmAuditLog.REALM_BILLING_MODALITY_CHANGED
|
return RealmAuditLog.REALM_BILLING_MODALITY_CHANGED
|
||||||
|
elif event_type is AuditLogEventType.CUSTOMER_PLAN_END_DATE_CHANGED:
|
||||||
|
return RealmAuditLog.CUSTOMER_PLAN_END_DATE_CHANGED # nocoverage
|
||||||
elif event_type is AuditLogEventType.CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN:
|
elif event_type is AuditLogEventType.CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN:
|
||||||
return RealmAuditLog.CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN
|
return RealmAuditLog.CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN
|
||||||
elif event_type is AuditLogEventType.CUSTOMER_SWITCHED_FROM_ANNUAL_TO_MONTHLY_PLAN:
|
elif event_type is AuditLogEventType.CUSTOMER_SWITCHED_FROM_ANNUAL_TO_MONTHLY_PLAN:
|
||||||
|
@ -3344,6 +3378,8 @@ class RemoteRealmBillingSession(BillingSession):
|
||||||
return RemoteRealmAuditLog.REMOTE_SERVER_SPONSORSHIP_PENDING_STATUS_CHANGED
|
return RemoteRealmAuditLog.REMOTE_SERVER_SPONSORSHIP_PENDING_STATUS_CHANGED
|
||||||
elif event_type is AuditLogEventType.BILLING_MODALITY_CHANGED:
|
elif event_type is AuditLogEventType.BILLING_MODALITY_CHANGED:
|
||||||
return RemoteRealmAuditLog.REMOTE_SERVER_BILLING_MODALITY_CHANGED # nocoverage
|
return RemoteRealmAuditLog.REMOTE_SERVER_BILLING_MODALITY_CHANGED # nocoverage
|
||||||
|
elif event_type is AuditLogEventType.CUSTOMER_PLAN_END_DATE_CHANGED:
|
||||||
|
return RemoteRealmAuditLog.CUSTOMER_PLAN_END_DATE_CHANGED
|
||||||
elif event_type is AuditLogEventType.BILLING_ENTITY_PLAN_TYPE_CHANGED:
|
elif event_type is AuditLogEventType.BILLING_ENTITY_PLAN_TYPE_CHANGED:
|
||||||
return RemoteRealmAuditLog.REMOTE_SERVER_PLAN_TYPE_CHANGED
|
return RemoteRealmAuditLog.REMOTE_SERVER_PLAN_TYPE_CHANGED
|
||||||
elif (
|
elif (
|
||||||
|
@ -3756,6 +3792,8 @@ class RemoteServerBillingSession(BillingSession):
|
||||||
return RemoteZulipServerAuditLog.REMOTE_SERVER_SPONSORSHIP_PENDING_STATUS_CHANGED
|
return RemoteZulipServerAuditLog.REMOTE_SERVER_SPONSORSHIP_PENDING_STATUS_CHANGED
|
||||||
elif event_type is AuditLogEventType.BILLING_MODALITY_CHANGED:
|
elif event_type is AuditLogEventType.BILLING_MODALITY_CHANGED:
|
||||||
return RemoteZulipServerAuditLog.REMOTE_SERVER_BILLING_MODALITY_CHANGED # nocoverage
|
return RemoteZulipServerAuditLog.REMOTE_SERVER_BILLING_MODALITY_CHANGED # nocoverage
|
||||||
|
elif event_type is AuditLogEventType.CUSTOMER_PLAN_END_DATE_CHANGED:
|
||||||
|
return RemoteZulipServerAuditLog.CUSTOMER_PLAN_END_DATE_CHANGED # nocoverage
|
||||||
elif event_type is AuditLogEventType.BILLING_ENTITY_PLAN_TYPE_CHANGED:
|
elif event_type is AuditLogEventType.BILLING_ENTITY_PLAN_TYPE_CHANGED:
|
||||||
return RemoteZulipServerAuditLog.REMOTE_SERVER_PLAN_TYPE_CHANGED
|
return RemoteZulipServerAuditLog.REMOTE_SERVER_PLAN_TYPE_CHANGED
|
||||||
elif (
|
elif (
|
||||||
|
|
|
@ -9,6 +9,16 @@
|
||||||
<button type="submit" class="btn btn-default support-submit-button">Update</button>
|
<button type="submit" class="btn btn-default support-submit-button">Update</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{% if current_plan.end_date and current_plan.status == current_plan.ACTIVE %}
|
||||||
|
<form method="POST" class="remote-form">
|
||||||
|
<b>Plan end date</b><br />
|
||||||
|
{{ csrf_input }}
|
||||||
|
<input type="hidden" name="{{ remote_type }}" value="{{ remote_id }}" />
|
||||||
|
<input type="date" name="plan_end_date" value="{{ current_plan.end_date.strftime('%Y-%m-%d') }}" required />
|
||||||
|
<button type="submit" class="btn btn-default support-submit-button">Update</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<form method="POST" class="remote-form">
|
<form method="POST" class="remote-form">
|
||||||
<b>Modify current plan</b><br />
|
<b>Modify current plan</b><br />
|
||||||
{{ csrf_input }}
|
{{ csrf_input }}
|
||||||
|
|
|
@ -102,6 +102,7 @@ class AbstractRealmAuditLog(models.Model):
|
||||||
CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN = 503
|
CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN = 503
|
||||||
CUSTOMER_SWITCHED_FROM_ANNUAL_TO_MONTHLY_PLAN = 504
|
CUSTOMER_SWITCHED_FROM_ANNUAL_TO_MONTHLY_PLAN = 504
|
||||||
CUSTOMER_MINIMUM_LICENSES_CHANGED = 505
|
CUSTOMER_MINIMUM_LICENSES_CHANGED = 505
|
||||||
|
CUSTOMER_PLAN_END_DATE_CHANGED = 506
|
||||||
|
|
||||||
STREAM_CREATED = 601
|
STREAM_CREATED = 601
|
||||||
STREAM_DEACTIVATED = 602
|
STREAM_DEACTIVATED = 602
|
||||||
|
|
Loading…
Reference in New Issue