2023-12-02 17:55:39 +01:00
|
|
|
from dataclasses import dataclass
|
2023-10-31 19:22:55 +01:00
|
|
|
from decimal import Decimal
|
2023-12-13 18:23:05 +01:00
|
|
|
from typing import Optional, TypedDict
|
2021-09-29 19:51:55 +02:00
|
|
|
from urllib.parse import urlencode, urljoin, urlunsplit
|
|
|
|
|
|
|
|
from django.conf import settings
|
|
|
|
from django.urls import reverse
|
2023-12-02 17:55:39 +01:00
|
|
|
from django.utils.timezone import now as timezone_now
|
2021-09-29 19:51:55 +02:00
|
|
|
|
2023-12-02 17:55:39 +01:00
|
|
|
from corporate.lib.stripe import BillingSession
|
2023-12-13 18:23:05 +01:00
|
|
|
from corporate.models import (
|
|
|
|
Customer,
|
|
|
|
CustomerPlan,
|
|
|
|
ZulipSponsorshipRequest,
|
|
|
|
get_current_plan_by_customer,
|
|
|
|
)
|
|
|
|
from zerver.models import Realm, get_org_type_display_name, get_realm
|
2023-12-06 18:16:20 +01:00
|
|
|
from zilencer.lib.remote_counts import MissingDataError
|
2021-09-29 19:51:55 +02:00
|
|
|
|
|
|
|
|
2023-12-13 18:23:05 +01:00
|
|
|
class SponsorshipRequestDict(TypedDict):
|
|
|
|
org_type: str
|
|
|
|
org_website: str
|
|
|
|
org_description: str
|
|
|
|
total_users: str
|
|
|
|
paid_users: str
|
|
|
|
paid_users_description: str
|
|
|
|
requested_plan: str
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class SponsorshipData:
|
|
|
|
sponsorship_pending: bool = False
|
|
|
|
default_discount: Optional[Decimal] = None
|
|
|
|
latest_sponsorship_request: Optional[SponsorshipRequestDict] = None
|
|
|
|
|
|
|
|
|
2023-12-02 17:55:39 +01:00
|
|
|
@dataclass
|
|
|
|
class PlanData:
|
|
|
|
customer: Optional["Customer"] = None
|
|
|
|
current_plan: Optional["CustomerPlan"] = None
|
|
|
|
licenses: Optional[int] = None
|
|
|
|
licenses_used: Optional[int] = None
|
2023-12-06 18:09:34 +01:00
|
|
|
is_legacy_plan: bool = False
|
2023-12-11 17:09:36 +01:00
|
|
|
has_fixed_price: bool = False
|
2023-12-06 18:16:20 +01:00
|
|
|
warning: Optional[str] = None
|
2023-12-02 17:55:39 +01:00
|
|
|
|
|
|
|
|
2023-12-13 18:23:05 +01:00
|
|
|
@dataclass
|
|
|
|
class SupportData:
|
|
|
|
plan_data: PlanData
|
|
|
|
sponsorship_data: SponsorshipData
|
|
|
|
|
|
|
|
|
2023-12-06 15:00:23 +01:00
|
|
|
def get_realm_support_url(realm: Realm) -> str:
|
2021-09-29 19:51:55 +02:00
|
|
|
support_realm_uri = get_realm(settings.STAFF_SUBDOMAIN).uri
|
|
|
|
support_url = urljoin(
|
|
|
|
support_realm_uri,
|
|
|
|
urlunsplit(("", "", reverse("support"), urlencode({"q": realm.string_id}), "")),
|
|
|
|
)
|
|
|
|
return support_url
|
2023-10-31 19:22:55 +01:00
|
|
|
|
|
|
|
|
2023-12-02 17:45:25 +01:00
|
|
|
def get_customer_discount_for_support_view(
|
|
|
|
customer: Optional[Customer] = None,
|
|
|
|
) -> Optional[Decimal]:
|
|
|
|
if customer is None:
|
|
|
|
return None
|
|
|
|
return customer.default_discount
|
2023-12-02 17:55:39 +01:00
|
|
|
|
|
|
|
|
2023-12-13 18:23:05 +01:00
|
|
|
def get_customer_sponsorship_data(customer: Customer) -> SponsorshipData:
|
|
|
|
pending = customer.sponsorship_pending
|
|
|
|
discount = customer.default_discount
|
|
|
|
sponsorship_request = None
|
|
|
|
if pending:
|
|
|
|
last_sponsorship_request = (
|
|
|
|
ZulipSponsorshipRequest.objects.filter(customer=customer).order_by("id").last()
|
|
|
|
)
|
|
|
|
if last_sponsorship_request is not None:
|
|
|
|
org_type_name = get_org_type_display_name(last_sponsorship_request.org_type)
|
|
|
|
if (
|
|
|
|
last_sponsorship_request.org_website is None
|
|
|
|
or last_sponsorship_request.org_website == ""
|
|
|
|
):
|
|
|
|
website = "No website submitted"
|
|
|
|
else:
|
|
|
|
website = last_sponsorship_request.org_website
|
|
|
|
sponsorship_request = SponsorshipRequestDict(
|
|
|
|
org_type=org_type_name,
|
|
|
|
org_website=website,
|
|
|
|
org_description=last_sponsorship_request.org_description,
|
|
|
|
total_users=last_sponsorship_request.expected_total_users,
|
|
|
|
paid_users=last_sponsorship_request.paid_users_count,
|
|
|
|
paid_users_description=last_sponsorship_request.paid_users_description,
|
|
|
|
requested_plan=last_sponsorship_request.requested_plan,
|
|
|
|
)
|
|
|
|
|
|
|
|
return SponsorshipData(
|
|
|
|
sponsorship_pending=pending,
|
|
|
|
default_discount=discount,
|
|
|
|
latest_sponsorship_request=sponsorship_request,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-12-02 17:55:39 +01:00
|
|
|
def get_current_plan_data_for_support_view(billing_session: BillingSession) -> PlanData:
|
|
|
|
customer = billing_session.get_customer()
|
|
|
|
plan = None
|
|
|
|
if customer is not None:
|
|
|
|
plan = get_current_plan_by_customer(customer)
|
|
|
|
plan_data = PlanData(
|
|
|
|
customer=customer,
|
|
|
|
current_plan=plan,
|
|
|
|
)
|
|
|
|
if plan is not None:
|
|
|
|
new_plan, last_ledger_entry = billing_session.make_end_of_cycle_updates_if_needed(
|
|
|
|
plan, timezone_now()
|
|
|
|
)
|
|
|
|
if last_ledger_entry is not None:
|
|
|
|
if new_plan is not None:
|
|
|
|
plan_data.current_plan = new_plan # nocoverage
|
|
|
|
plan_data.licenses = last_ledger_entry.licenses
|
2023-12-06 18:16:20 +01:00
|
|
|
try:
|
|
|
|
plan_data.licenses_used = billing_session.current_count_for_billed_licenses()
|
|
|
|
except MissingDataError: # nocoverage
|
|
|
|
plan_data.warning = "Recent data missing: No information for used licenses"
|
2023-12-06 18:09:34 +01:00
|
|
|
assert plan_data.current_plan is not None # for mypy
|
|
|
|
plan_data.is_legacy_plan = (
|
|
|
|
plan_data.current_plan.tier == CustomerPlan.TIER_SELF_HOSTED_LEGACY
|
|
|
|
)
|
2023-12-11 17:09:36 +01:00
|
|
|
plan_data.has_fixed_price = plan_data.current_plan.fixed_price is not None
|
2023-12-02 17:55:39 +01:00
|
|
|
|
|
|
|
return plan_data
|
2023-12-13 18:23:05 +01:00
|
|
|
|
|
|
|
|
|
|
|
def get_data_for_support_view(billing_session: BillingSession) -> SupportData:
|
|
|
|
plan_data = get_current_plan_data_for_support_view(billing_session)
|
|
|
|
customer = billing_session.get_customer()
|
|
|
|
if customer is not None:
|
|
|
|
sponsorship_data = get_customer_sponsorship_data(customer)
|
|
|
|
else:
|
|
|
|
sponsorship_data = SponsorshipData()
|
|
|
|
|
|
|
|
return SupportData(
|
|
|
|
plan_data=plan_data,
|
|
|
|
sponsorship_data=sponsorship_data,
|
|
|
|
)
|