diff --git a/docker-app/qfieldcloud/core/admin.py b/docker-app/qfieldcloud/core/admin.py index c67e87f70..b14d6f35d 100644 --- a/docker-app/qfieldcloud/core/admin.py +++ b/docker-app/qfieldcloud/core/admin.py @@ -1051,14 +1051,13 @@ class OrganizationAdmin(QFieldCloudModelAdmin): "email", "organization_owner", "date_joined", + "active_users_links", ) list_display = ( "username", "email", "organization_owner__link", "date_joined", - # "storage_usage__field", - "active_users", ) search_fields = ( @@ -1068,22 +1067,31 @@ class OrganizationAdmin(QFieldCloudModelAdmin): "organization_owner__email__iexact", ) - readonly_fields = ("date_joined", "storage_usage__field", "active_users") + readonly_fields = ( + "date_joined", + "storage_usage__field", + "active_users_links", + ) - list_select_related = ("organization_owner",) + list_select_related = ("organization_owner", "useraccount") list_filter = ("date_joined",) autocomplete_fields = ("organization_owner",) - @admin.display(description=_("Active users (last billing period)")) - def active_users(self, instance) -> int | None: - # The relation 'current_subscription_vw' is not instantiated unless the organization - # does have a current subscription - if hasattr(instance, "current_subscription_vw"): - return instance.current_subscription_vw.active_users_count - else: - return None + @admin.display(description=_("Active members")) + def active_users_links(self, instance) -> str: + persons = instance.useraccount.current_subscription.active_users + userlinks = "

-

" + if persons: + userlinks = "
".join(model_admin_url(p, p.username) for p in persons) + help_text = """ +

+ Active members have triggererd at least one job or uploaded at least one delta in the current billing period. + These are all the users who will be billed -- plan included or additional. +

+ """ + return format_html(f"{userlinks} {help_text}") @admin.display(description=_("Owner")) def organization_owner__link(self, instance): diff --git a/docker-app/qfieldcloud/subscription/models.py b/docker-app/qfieldcloud/subscription/models.py index 3c03dbd46..3f8b1271c 100644 --- a/docker-app/qfieldcloud/subscription/models.py +++ b/docker-app/qfieldcloud/subscription/models.py @@ -479,11 +479,14 @@ class UpdateSubscriptionKwargs(TypedDict): billing_cycle_anchor_at = models.DateTimeField(null=True, blank=True) # the timestamp when the current billing period started - # NOTE this field remains currently unused. + # NOTE ignored when checking if subscription is 'current' (see `active_since and `active_until`). + # `current_period_since` and `current_period_until` are both either `None` or `datetime` current_period_since = models.DateTimeField(null=True, blank=True) # the timestamp when the current billing period ends - # NOTE ignored for subscription validity checks, but used to calculate the activation date when additional packages change + # NOTE ignored when checking if subscription is 'current' (see `active_since and `active_until`), + # but used to calculate the activation date when additional packages change. + # `current_period_since` and `current_period_until` are both either `None` or `datetime` current_period_until = models.DateTimeField(null=True, blank=True) # admin only notes, not visible to end users @@ -579,6 +582,7 @@ def has_current_period(self) -> bool: @property def active_users(self): + "Returns the queryset of active users for running stripe billing period or None." if not self.account.user.is_organization: return None @@ -588,17 +592,7 @@ def active_users(self): self.current_period_until, ) - assert ( - self.current_period_since == self.current_period_until - ), "Both current_period _since and _until must be set." - - now = timezone.now() - month_ago = now - timedelta(days=28) - - return self.account.user.active_users( - month_ago, - now, - ) + return None @property def active_users_count(self) -> int: @@ -606,7 +600,7 @@ def active_users_count(self) -> int: if not self.account.user.is_organization: return 1 - if not self.current_period_since or not self.current_period_until: + if self.active_users is None: return 0 return self.active_users.count()