Project

General

Profile

Feature #11983

Updated by Divya Inapakurthi 2 days ago

getTenantBillingSummary properly falls back to the platform setting and returns allowTenantSelfCancellation to the frontend? 

  
 cancelTenantSubscription honors both the per-tenant override and the platform fallback? 

  
 Any relevant unit tests are updated/added 

 when creating a new tenant, allowTenantSelfCancellation should defaults to the company setting allowTenantSelfCancellation value... is this correct way of configuration ? 

 The short answer is no, copying/freezing the value at creation time is generally an anti-pattern. Instead, the best practice for multi-tenant SaaS architecture is Hierarchical Inheritance (Cascading 3-State Configuration). 

 Why copying at tenant creation causes issues 

 If you copy the platform/company default into newTenant.saasBilling.allowTenantSelfCancellation at creation time: 


  
 Loss of Global Policy Control: 
 If the platform superadmin later changes the platform setting in PlatformBillingSettingsPage.tsx (which advertises: "Platform billing settings updated successfully across all tenants"), none of the existing tenants will reflect the change because their values were hardcoded on creation. 

  
 Database Migration Overhead: 
 Any time policy changes globally, you would need to run a batch database migration across all tenant documents instead of simply changing a single platform-level setting. 

  
 The Recommended Pattern: Hierarchical Inheritance (3-State) 

 Instead of hardcoding a boolean at creation, leave the tenant setting as null (unset), and dynamically resolve the effective value using a fallback chain: 

 Effective Value 
 = 
 Tenant Override 
 ( 
 if not null 
 ) 
 ⟶ 
 Platform/Company Setting 
 ⟶ 
 Hardcoded Fallback (false) 
 Effective Value=Tenant Override(if not null)⟶Platform/Company Setting⟶Hardcoded Fallback (false) 

 In this model: 


  
 null (Default for every new tenant)  
 → 
 → "Inherit Global Platform Setting". 

  
 true  
 → 
 → Explicit Override: Force-enabled for this specific tenant (even if platform default is disabled). 

  
 false  
 → 
 → Explicit Override: Force-disabled for this specific tenant (even if platform default is enabled). 

  
 Evidence Already in Your Codebase 

 Your codebase was actually designed for this hierarchical model: 


  
 In company-model.js: 
  
 javascript 
 // Line 1059: Tenant-level override defaults to null 
 saasBilling: { 
   allowTenantSelfCancellation: { type: Boolean, default: null }, 
 }, 
 // Line 1079: Platform-level default 
 billingSettings: { 
   allowTenantSelfCancellation: { type: Boolean, default: false }, 
 } 

  

  
 In billing.controller.js: 
 toggleTenantSelfCancellation specifically allows superadmins to override this per company. 

  
 Current Inconsistencies to Fix in Your Logic 

 While the schema was designed for hierarchical inheritance, a few places in tenantBilling.service.js have conflicting or incomplete resolution logic: 

 1. In resolveBillingContext (tenantBilling.service.js#L586): 

 It currently does: 

 javascript 
 // Boolean(null) becomes false, ignoring the global setting! 
 allowTenantSelfCancellation: billing.status === "trialing" || Boolean(billing.allowTenantSelfCancellation), 
 2. In cancelTenantSubscription (tenantBilling.service.js#L3440-L3446): 

 It currently checks only globalSettings.allowTenantSelfCancellation and ignores company.saasBilling?.allowTenantSelfCancellation. If a superadmin overrides a specific tenant to allow self-cancellation, the backend still blocks them. 

 3. In getTenantBillingSummary (tenantBilling.service.js#L1780-L1903): 

 PlanUsagePage.tsx#L638 expects data.allowTenantSelfCancellation at the top level of the API response, but getTenantBillingSummary defines allowTenantSelfCancellation on line 1781 and never attaches it to the return object. 

 Suggested Helper for Resolution 

 Create a unified helper in tenantBilling.service.js: 

 javascript 
 const resolveAllowTenantSelfCancellation = async (company) => { 
   // Always allow cancellation during trialing period 
   if (company.saasBilling?.status === "trialing") return true; 
   // 1. Check tenant explicit override (true / false) 
   if (company.saasBilling?.allowTenantSelfCancellation != null) { 
     return Boolean(company.saasBilling.allowTenantSelfCancellation); 
   } 
   // 2. Fall back to platform global default 
   const globalSettings = await getPlatformBillingSettings(); 
   return Boolean(globalSettings.allowTenantSelfCancellation); 
 }; 
 Summary Recommendation 

  
 Do not copy the setting to the tenant record on creation; leave it as null. 

  
 Use the fallback/inheritance pattern at runtime so platform-wide changes take effect automatically, while preserving the ability for superadmins to set specific per-tenant overrides. 

  

Back