Adds a link to the plan ledger view in the current plan information
shown in the support views. Link is not shown if the plan is 100%
sponsored, e.g., the Community plan.
Adds a formatted header area to the activity table template so
that it's easy to add useful information to these activity views.
Adds non-form section to Zulip Cloud support view with some basic
realm information: organization type, plan type, non-guest user
count and guest user count.
Uses a shared template for the basic realm data and adds a shared
support context dict for variables that are used in both remote
and Zulip Cloud support views.
Gives a baseline of current database queries for these single realm
or remote realm support views, so that as we add features to these
views, we can better manage how those changes impact the performance
of our support views in general.
There are a few places where we want to set the max invites for a
realm to the default for a realm's plan type, so this creates a
helper function that can be used consistently to get that default
value.
Generated using manage.py squashmigrations, with some work:
- Used my patch to support squashing AddConstraint/RemoveConstraint
operations.
- Manually removed the add/deletion of cloud_xor_self_hosted, since it
didn't squash properly.
- Temporarily removed a couple operations from their migration files,
and added them back manually both to the original file and the
squash file, to allow later operations to squash
properly. Specifically, these are the two unsquashed operations
documented with comments at the end of the squash migration file.
Adds some validation for changing the realm's max invites via the
support view so that it is not set below the default max for the
realm's plan type, and so that if it's currently set to the default
max it's not reset to that same value.
In the Zulip Cloud support view, adds a "Realm management" section
for support actions that are specific to that view, (e.g., changing
an organization's type or deactivating an organization).
Moves the note about emailing organization owners when a full
sponsorship is approved for Zulip Cloud Standard to the success
message for that action, which mirrors the remote server/realm
support view.
Expands section for scheduling plans in the remote support view to
have a form to create a temporary courtesy plan (aka our legacy
plan for remote servers and realms).
Form is not shown if there is a current plan for the remote billing
entity, and would raise a SupportRequestError in that case as well.
In commit 7203661d99, we removed the default_discount field on the
Customer model, but we didn't update the remote support views for
this change.
Adds a has_discount boolean to the SponsorshipData that's used in
the support views for headers, and updates the discount information
on a deactivated remote realm to show any pre-existing discounted
prices.
Instead of the PUSH_NOTIFICATIONS_BOUNCER_URL and
SUBMIT_USAGE_STATISTICS settings, we want servers to configure
individual ZULIP_SERVICE_* settings, while maintaining backward
compatibility with the old settings. Thus, if all the new
ZULIP_SERVICE_* are at their default False value, but the legacy
settings are activated, they need to be translated in computed_settings
to the modern way.
When using the sub-expression purely for filtering, and not for
accessing the value in the resultset, .alias() is potentially faster
since it does not pull the value in as well.
migrate the following endpoints from @has_request_variables
to @typed_endpoint :
- upgrade()
- remote_realm_upgrade()
- upgrade_page()
- remote_server_upgrade()
Creates a new "realm_deactivated" email that can be sent to realm
owners as part of `do_deactivate_realm`, via a boolean flag,
`email_owners`.
This flag is set to `False` when `do_deactivate_realm` is used for
realm exports or changing a realm's subdomain, so that the active
organization owners are not emailed in those cases.
This flag is optional for the `deactivate_realm` management command,
but as there is no active user passed in that case, then the email
is sent without referencing who deactivated the realm.
It is passed as `True` for the support analytics view, but the email
that is generated does not include information about the support
admin user who completed the request for organization deactivation.
When an active organization owner deactivates the organization, then
the flag is `True` and an email is sent to them as well as any other
active organization owners, with a slight variation in the email text
for those two cases.
Adds specific tests for when `email_owners` is passed as `True`. All
existing tests for other functionality of `do_deactivate_user` pass
the flag as `False`.
Adds `localize` from django.util.formats as a jinja env filter so
that the dates in these emails are internationlized for the owner's
default language setting in the "realm_deactivated" email templates.
Fixes#24685.
Migrate all `ids` of anything which does not have a foreign key from
the Message or UserMessage table (and would thus require walking
those) to be `bigint`. This is done by removing explicit
`BigAutoField`s, trading them for explicit `AutoField`s on the tables
to not be migrated, while updating `DEFAULT_AUTO_FIELD` to the new
default.
In general, the tables adjusted in this commit are small tables -- at
least compared to Messages and UserMessages.
Many-to-many tables without their own model class are adjusted by a
custom Operation, since they do not automatically pick up migrations
when `DEFAULT_AUTO_FIELD` changes[^1].
Note that this does multiple scans over tables to update foreign
keys[^2]. Large installs may wish to hand-optimize this using the
output of `./manage.py sqlmigrate` to join multiple `ALTER TABLE`
statements into one, to speed up the migration. This is unfortunately
not possible to do generically, as constraint names may differ between
installations.
This leaves the following primary keys as non-`bigint`:
- `auth_group.id`
- `auth_group_permissions.id`
- `auth_permission.id`
- `django_content_type.id`
- `django_migrations.id`
- `otp_static_staticdevice.id`
- `otp_static_statictoken.id`
- `otp_totp_totpdevice.id`
- `two_factor_phonedevice.id`
- `zerver_archivedmessage.id`
- `zerver_client.id`
- `zerver_message.id`
- `zerver_realm.id`
- `zerver_recipient.id`
- `zerver_userprofile.id`
[^1]: https://code.djangoproject.com/ticket/32674
[^2]: https://code.djangoproject.com/ticket/24203
In the activity and support views, we want to see the annual
revenue for fixed price plans. While on billing pages, we do
not display this information as these plans are renegotiated
annually.
Adds get_annual_recurring_revenue_for_support_data function
to BillingSession class, so that we can get the fixed price
plan data for these views without changing the logic for
what is displayed on the billing pages.
Adds a link to the stripe customer dashboard if the Customer
object for an active plan has a stripe_customer_id. If there
is no stripe ID to link to, then the icon is shown without
a link, which is the case for remote server/realm sponsorships
and legacy plans.
It's going to be helpful in the future to record the reason for realm
deactivation.
- For information tracking
- For making a distinction between cases where we can allow realm owners
to reactivate their realm via a self-serve flow (e.g.
"owner_request") vs where we can't (ToS abuse).
This commit adds include_realm_default_subscriptions parameter
to the invite endpoints and the corresponding field in
PreregistrationUser and MultiuseInvite objects. This field will
be used to subscribe the new users to the default streams at the
time of account creation and not to the streams that were default
when sending the invite.
In #23380, we are changing all occurrences of uri with url in order to
follow the latest URL standard. Previous PRs #25038 and #25045 has
replaced the occurences of uri that has no direct relation with realm.
This commit changes just the model property, which has no API
compatibility concerns.
This prevents users from hammering the invitation endpoint, causing
races, and inviting more users than they should otherwise be allowed
to.
Doing this requires that we not raise InvitationError when we have
partially succeeded; that behaviour is left to the one callsite of
do_invite_users.
Reported by Lakshit Agarwal (@chiekosec).
We use `error_description` in upgrade.ts to determine if the error was
related to customer's card. Doesn't seem like there is any harm
in doing so since we are explicitly handling "stripe.CardError" and
raising these errors with "card error" description.
This also now allows user to upgrade to plus plan from pricing page.
Note that since we don't pass customer_plan on pages like self-hosting
and for/business, `Current plan` status is not displayed on these pages.
Earlier, we were not verifying that the invoice which got paid is
for the fixed-price plan.
That could result in a bug where another support invoice with
collection_method = "send_invoice" got paid while a fixed-price
plam is already configured. The fixed-price plan would be falsely
activated.
This commit verifies the invoice before activating the fixed-price
plan.
For simiplicty's sake, we can avoid trying to do cache invalidation in
the variety of events that can cause the seat count to change - since
having an up to 1 day delay between users being added and the upload
limit going up is quite reasonable.
This might not be the most meaningful change of phrasing, but .is_paid()
sounds like it's a check for whether the customer has already paid their
invoice. is_a_paid_plan() reflects better the meaning that it's whether
it's a plan of a "paid" type.
We send customer an invoice at the start of free trial, if customer
pays we upgrade them to the active plan at the end of free trial,
else we downgrade them and show a custom message on the upgrade
page regarding the current status.
Tests were broken since #29221 and #28875 didn't account for
other tests failing due to changed stripe data. Also, there
was a bug where we were not fetching the correct setup intent
and stripe session for the current test, it was fixed by narrowing
the fetch to the current customer.
Also, we now run `invoice_plans` in a `while` loop until
`next_invoice_date` is greater than the provided event_time. It
makes sense to generate all the invoices for a customer that
needs to paid by them when `invoice_plans_as_needed` is called
for a `event_time`.
Earlier, when adding a new user failed due to no spare licenses
available, a message was sent to the "New user announcements"
stream.
We plan to disable the stream by default as a part of improving
onboarding experience.
Now, we send a group DM to admins when adding a new user fails
due to no spare licenses available. It makes it independent of
the "New user announcements" setting. These warning messages
are important and shouldn't be missed.
As explained in the comment, when we're moving the server plan to the
remote realm's Customer object, the realm Customer may not have
stripe_customer_id set and therefore that value needs to get moved from
the server Customer.
Earlier, if a free plan (say legacy plan) with no next plan scheduled
was invoiced, we used to send an invoice overdue email if the last
audit log update is stale.
Actually, we don't need this data as the invoice step is just going
to downgrade the current plan. We should not wait for customer to
start uploading data in this case. Skip the email sending step and
invoice the plan to downgrade.
This decorator, among other things, transforms the "event" argument
passed when calling the decorated functions into actually passing
event.content_object.
So e.g. despite having a (before the decorator is applied) signature:
```
def handle_invoice_paid_event(stripe_invoice: stripe.Invoice, invoice: Invoice) -> None:
```
these are called passing an `Event` in the second arg when calling
`handle_invoice_paid_event`:
```
handle_invoice_paid_event(stripe_invoice, event)
```
I found that kind of confusing because the @error_handler decorator
didn't sound like something that would intervene in the arguments like
that. So it feels helpful to rename it something with a less modest
name, that makes it sound like it does more than just pure
error-handling.
Adds a line to the top of the internal_billing_notice email with
the billing entity's display name.
Makes sure all internal_billng_notice email subjects also include
the billing entity's display name.
Makes small updates to the notice text for some cases.
When a customer with plan's status 'DOWNGRADE_AT_END_OF_FREE_TRIAL'
visits /billing page on the free-trial end date before the invoice
cron runs, the 'make_end_of_cycle_updates_if_needed' downgrades the
plan.
Earlier, when such a customer visited /billing page in this time window
it resulted in an assertion error.
This commit fixes the incorrect behaviour by redirecting to '/plans'
page in such cases.
No renewal ledger entry exists for a scheduled fixed-price plan.
Earlier, when a customer with scheduled fixed-price plan
visited the billing page, the 'get_next_billing_cycle' function
raised an assertion error as no renewal ledger entry exists.
We don't need the ledger entry to determine the next billing
cycle in this case, so we move the code block which determines
the last_renewal time within the else conditional i.e. where it
is needed.
The scheduled plan has NEVER_STARTED status and the
next_invoice_date set to end date of the current plan.
We use this info to determine the next billing cycle.
If the remote realm registered after the legacy plan on server
ENDED, we never migrate the plan to the remote realm. So, we need
to check the server of remote realm whenever we are check remote
realm for legacy plan.
Regardless of plan renewal schedule, we try to invoice all plans
monthly with some exceptions like free trial and fixed price plans,
which help us charge users for additional licenses used during
the previous month.
Includes has_stale_audit_log boolean in the remote support view
data so that we can style the last audit log data for the remote
server to visually highlight if it is stale or current.
Refactors the Cloud support view to pass in any sponsorship or
discount information about the Customer object for the realm,
which allows us to display any information submitted in a
sponsorship request.