mirror of https://github.com/zulip/zulip.git
billing: Enforce manual billing renewal licenses for new users.
In addition to checking for available licenses in the current billing period when adding or inviting new non-guest users, for manual billing, we also verify that the number of licenses set for the next billing period will be enough when adding/inviting new users. Realms that are exempt from license number checks do not have this restriction applied. Admins are notified via group direct message when a user fails to register due to this restriction.
This commit is contained in:
parent
73cb08265c
commit
7861c1ba63
|
@ -60,13 +60,13 @@ def send_user_unable_to_signup_group_direct_message_to_admins(
|
||||||
realm: Realm, user_email: str
|
realm: Realm, user_email: str
|
||||||
) -> None:
|
) -> None:
|
||||||
message = _(
|
message = _(
|
||||||
"A new member ({email}) was unable to join your organization because all Zulip licenses "
|
"A new user ({email}) was unable to join because your organization does not have enough "
|
||||||
"are in use. Please [increase the number of licenses]({billing_page_link}) or "
|
"Zulip licenses. To allow new users to join, make sure that the [number of licenses for "
|
||||||
"[deactivate inactive users]({deactivate_user_help_page_link}) to allow new members to join."
|
"the current and next billing period]({billing_page_link}) is greater than the current "
|
||||||
|
"number of users."
|
||||||
).format(
|
).format(
|
||||||
email=user_email,
|
email=user_email,
|
||||||
billing_page_link="/billing/",
|
billing_page_link="/billing/",
|
||||||
deactivate_user_help_page_link="/help/deactivate-or-reactivate-a-user",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
send_group_direct_message_to_admins(
|
send_group_direct_message_to_admins(
|
||||||
|
@ -77,9 +77,12 @@ def send_user_unable_to_signup_group_direct_message_to_admins(
|
||||||
def check_spare_licenses_available(
|
def check_spare_licenses_available(
|
||||||
realm: Realm, plan: CustomerPlan, extra_non_guests_count: int = 0, extra_guests_count: int = 0
|
realm: Realm, plan: CustomerPlan, extra_non_guests_count: int = 0, extra_guests_count: int = 0
|
||||||
) -> None:
|
) -> None:
|
||||||
if plan.licenses() < get_seat_count(
|
seat_count = get_seat_count(
|
||||||
realm, extra_non_guests_count=extra_non_guests_count, extra_guests_count=extra_guests_count
|
realm, extra_non_guests_count=extra_non_guests_count, extra_guests_count=extra_guests_count
|
||||||
):
|
)
|
||||||
|
current_licenses = plan.licenses()
|
||||||
|
renewal_licenses = plan.licenses_at_next_renewal()
|
||||||
|
if current_licenses < seat_count or (renewal_licenses and renewal_licenses < seat_count):
|
||||||
raise LicenseLimitError
|
raise LicenseLimitError
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,7 +108,6 @@ def check_spare_licenses_available_for_registering_new_user(
|
||||||
def check_spare_licenses_available_for_inviting_new_users(
|
def check_spare_licenses_available_for_inviting_new_users(
|
||||||
realm: Realm, extra_non_guests_count: int = 0, extra_guests_count: int = 0
|
realm: Realm, extra_non_guests_count: int = 0, extra_guests_count: int = 0
|
||||||
) -> None:
|
) -> None:
|
||||||
num_invites = extra_non_guests_count + extra_guests_count
|
|
||||||
plan = get_plan_if_manual_license_management_enforced(realm)
|
plan = get_plan_if_manual_license_management_enforced(realm)
|
||||||
if plan is None:
|
if plan is None:
|
||||||
return
|
return
|
||||||
|
@ -113,10 +115,7 @@ def check_spare_licenses_available_for_inviting_new_users(
|
||||||
try:
|
try:
|
||||||
check_spare_licenses_available(realm, plan, extra_non_guests_count, extra_guests_count)
|
check_spare_licenses_available(realm, plan, extra_non_guests_count, extra_guests_count)
|
||||||
except LicenseLimitError:
|
except LicenseLimitError:
|
||||||
if num_invites == 1:
|
|
||||||
message = _("All Zulip licenses for this organization are currently in use.")
|
|
||||||
else:
|
|
||||||
message = _(
|
message = _(
|
||||||
"Your organization does not have enough unused Zulip licenses to invite {num_invites} users."
|
"Your organization does not have enough Zulip licenses. Invitations were not sent."
|
||||||
).format(num_invites=num_invites)
|
)
|
||||||
raise InvitationError(message, [], sent_invitations=False, license_limit_reached=True)
|
raise InvitationError(message, [], sent_invitations=False, license_limit_reached=True)
|
||||||
|
|
|
@ -535,19 +535,24 @@ class InviteUserTest(InviteUserBase):
|
||||||
result = self.invite(self.nonreg_email("alice"), ["Denmark"])
|
result = self.invite(self.nonreg_email("alice"), ["Denmark"])
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
|
|
||||||
ledger.licenses_at_next_renewal = 5
|
ledger.licenses_at_next_renewal = get_latest_seat_count(user.realm)
|
||||||
ledger.save(update_fields=["licenses_at_next_renewal"])
|
ledger.save(update_fields=["licenses_at_next_renewal"])
|
||||||
with self.settings(BILLING_ENABLED=True):
|
with self.settings(BILLING_ENABLED=True):
|
||||||
result = self.invite(self.nonreg_email("bob"), ["Denmark"])
|
result = self.invite(self.nonreg_email("bob"), ["Denmark"])
|
||||||
self.assert_json_success(result)
|
self.assert_json_error_contains(
|
||||||
|
result,
|
||||||
|
"Your organization does not have enough Zulip licenses. Invitations were not sent.",
|
||||||
|
)
|
||||||
|
|
||||||
|
ledger.licenses_at_next_renewal = 50
|
||||||
ledger.licenses = get_latest_seat_count(user.realm) + 1
|
ledger.licenses = get_latest_seat_count(user.realm) + 1
|
||||||
ledger.save(update_fields=["licenses"])
|
ledger.save(update_fields=["licenses", "licenses_at_next_renewal"])
|
||||||
with self.settings(BILLING_ENABLED=True):
|
with self.settings(BILLING_ENABLED=True):
|
||||||
invitee_emails = self.nonreg_email("bob") + "," + self.nonreg_email("alice")
|
invitee_emails = self.nonreg_email("bob") + "," + self.nonreg_email("alice")
|
||||||
result = self.invite(invitee_emails, ["Denmark"])
|
result = self.invite(invitee_emails, ["Denmark"])
|
||||||
self.assert_json_error_contains(
|
self.assert_json_error_contains(
|
||||||
result, "Your organization does not have enough unused Zulip licenses to invite 2 users"
|
result,
|
||||||
|
"Your organization does not have enough Zulip licenses. Invitations were not sent.",
|
||||||
)
|
)
|
||||||
|
|
||||||
ledger.licenses = get_latest_seat_count(user.realm)
|
ledger.licenses = get_latest_seat_count(user.realm)
|
||||||
|
@ -555,7 +560,8 @@ class InviteUserTest(InviteUserBase):
|
||||||
with self.settings(BILLING_ENABLED=True):
|
with self.settings(BILLING_ENABLED=True):
|
||||||
result = self.invite(self.nonreg_email("bob"), ["Denmark"])
|
result = self.invite(self.nonreg_email("bob"), ["Denmark"])
|
||||||
self.assert_json_error_contains(
|
self.assert_json_error_contains(
|
||||||
result, "All Zulip licenses for this organization are currently in use"
|
result,
|
||||||
|
"Your organization does not have enough Zulip licenses. Invitations were not sent.",
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.settings(BILLING_ENABLED=True):
|
with self.settings(BILLING_ENABLED=True):
|
||||||
|
|
|
@ -3026,7 +3026,7 @@ class UserSignUpTest(ZulipTestCase):
|
||||||
last_message = Message.objects.last()
|
last_message = Message.objects.last()
|
||||||
assert last_message is not None
|
assert last_message is not None
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
f"A new member ({self.nonreg_email('test')}) was unable to join your organization because all Zulip",
|
f"A new user ({self.nonreg_email('test')}) was unable to join because your organization",
|
||||||
last_message.content,
|
last_message.content,
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -3044,7 +3044,27 @@ class UserSignUpTest(ZulipTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
ledger.licenses = 50
|
ledger.licenses = 50
|
||||||
ledger.save(update_fields=["licenses"])
|
ledger.licenses_at_next_renewal = 5
|
||||||
|
ledger.save(update_fields=["licenses", "licenses_at_next_renewal"])
|
||||||
|
with self.settings(BILLING_ENABLED=True):
|
||||||
|
form = HomepageForm({"email": self.nonreg_email("test")}, realm=realm)
|
||||||
|
self.assertIn(
|
||||||
|
"New members cannot join this organization because all Zulip licenses",
|
||||||
|
form.errors["email"][0],
|
||||||
|
)
|
||||||
|
last_message = Message.objects.last()
|
||||||
|
assert last_message is not None
|
||||||
|
self.assertIn(
|
||||||
|
f"A new user ({self.nonreg_email('test')}) was unable to join because your organization",
|
||||||
|
last_message.content,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
set(get_direct_message_group_user_ids(last_message.recipient)),
|
||||||
|
expected_group_direct_message_user_ids,
|
||||||
|
)
|
||||||
|
|
||||||
|
ledger.licenses_at_next_renewal = 50
|
||||||
|
ledger.save(update_fields=["licenses_at_next_renewal"])
|
||||||
with self.settings(BILLING_ENABLED=True):
|
with self.settings(BILLING_ENABLED=True):
|
||||||
form = HomepageForm({"email": self.nonreg_email("test")}, realm=realm)
|
form = HomepageForm({"email": self.nonreg_email("test")}, realm=realm)
|
||||||
self.assertEqual(form.errors, {})
|
self.assertEqual(form.errors, {})
|
||||||
|
|
Loading…
Reference in New Issue