Files
bakery-ia/frontend/src/api/services/subscription.ts

240 lines
7.4 KiB
TypeScript
Raw Normal View History

import { apiClient } from '../client';
2025-09-20 08:59:12 +02:00
import {
SubscriptionLimits,
FeatureCheckResponse,
UsageCheckResponse,
UsageSummary,
AvailablePlans,
PlanUpgradeValidation,
2025-09-23 22:11:34 +02:00
PlanUpgradeResult,
SUBSCRIPTION_PLANS,
ANALYTICS_LEVELS,
AnalyticsLevel,
SubscriptionPlanKey,
PLAN_HIERARCHY,
ANALYTICS_HIERARCHY
} from '../types/subscription';
2025-09-23 22:11:34 +02:00
// Map plan keys to analytics levels based on backend data
const PLAN_TO_ANALYTICS_LEVEL: Record<SubscriptionPlanKey, AnalyticsLevel> = {
[SUBSCRIPTION_PLANS.STARTER]: ANALYTICS_LEVELS.BASIC,
[SUBSCRIPTION_PLANS.PROFESSIONAL]: ANALYTICS_LEVELS.ADVANCED,
[SUBSCRIPTION_PLANS.ENTERPRISE]: ANALYTICS_LEVELS.PREDICTIVE
};
// Cache for available plans
let cachedPlans: AvailablePlans | null = null;
let lastFetchTime: number | null = null;
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
export class SubscriptionService {
2025-10-06 15:27:01 +02:00
private readonly baseUrl = '/tenants';
async getSubscriptionLimits(tenantId: string): Promise<SubscriptionLimits> {
2025-10-06 15:27:01 +02:00
return apiClient.get<SubscriptionLimits>(`${this.baseUrl}/subscriptions/${tenantId}/limits`);
}
async checkFeatureAccess(
2025-10-06 15:27:01 +02:00
tenantId: string,
featureName: string
): Promise<FeatureCheckResponse> {
return apiClient.get<FeatureCheckResponse>(
2025-10-06 15:27:01 +02:00
`${this.baseUrl}/subscriptions/${tenantId}/features/${featureName}/check`
);
}
async checkUsageLimit(
2025-10-06 15:27:01 +02:00
tenantId: string,
resourceType: 'users' | 'sales_records' | 'inventory_items' | 'api_requests',
requestedAmount?: number
): Promise<UsageCheckResponse> {
const queryParams = new URLSearchParams();
if (requestedAmount !== undefined) {
queryParams.append('requested_amount', requestedAmount.toString());
}
const url = queryParams.toString()
2025-10-06 15:27:01 +02:00
? `${this.baseUrl}/subscriptions/${tenantId}/usage/${resourceType}/check?${queryParams.toString()}`
: `${this.baseUrl}/subscriptions/${tenantId}/usage/${resourceType}/check`;
return apiClient.get<UsageCheckResponse>(url);
}
async recordUsage(
tenantId: string,
resourceType: 'users' | 'sales_records' | 'inventory_items' | 'api_requests',
amount: number = 1
): Promise<{ success: boolean; message: string }> {
return apiClient.post<{ success: boolean; message: string }>(
2025-10-06 15:27:01 +02:00
`${this.baseUrl}/subscriptions/${tenantId}/usage/${resourceType}/record`,
{ amount }
);
}
async getCurrentUsage(tenantId: string): Promise<{
users: number;
sales_records: number;
inventory_items: number;
api_requests_this_hour: number;
}> {
2025-10-06 15:27:01 +02:00
return apiClient.get(`${this.baseUrl}/subscriptions/${tenantId}/usage/current`);
}
2025-09-20 08:59:12 +02:00
async getUsageSummary(tenantId: string): Promise<UsageSummary> {
2025-10-06 15:27:01 +02:00
return apiClient.get<UsageSummary>(`${this.baseUrl}/subscriptions/${tenantId}/usage`);
2025-09-20 08:59:12 +02:00
}
async getAvailablePlans(): Promise<AvailablePlans> {
2025-09-25 14:30:47 +02:00
return apiClient.get<AvailablePlans>('/plans');
2025-09-20 08:59:12 +02:00
}
async validatePlanUpgrade(tenantId: string, planKey: string): Promise<PlanUpgradeValidation> {
2025-10-07 07:15:07 +02:00
return apiClient.get<PlanUpgradeValidation>(`${this.baseUrl}/subscriptions/${tenantId}/validate-upgrade/${planKey}`);
2025-09-20 08:59:12 +02:00
}
async upgradePlan(tenantId: string, planKey: string): Promise<PlanUpgradeResult> {
2025-10-07 07:15:07 +02:00
return apiClient.post<PlanUpgradeResult>(`${this.baseUrl}/subscriptions/${tenantId}/upgrade?new_plan=${planKey}`, {});
2025-09-21 13:27:50 +02:00
}
async canAddLocation(tenantId: string): Promise<{ can_add: boolean; reason?: string; current_count?: number; max_allowed?: number }> {
2025-10-07 07:15:07 +02:00
return apiClient.get(`${this.baseUrl}/subscriptions/${tenantId}/can-add-location`);
2025-09-21 13:27:50 +02:00
}
async canAddProduct(tenantId: string): Promise<{ can_add: boolean; reason?: string; current_count?: number; max_allowed?: number }> {
2025-10-07 07:15:07 +02:00
return apiClient.get(`${this.baseUrl}/subscriptions/${tenantId}/can-add-product`);
2025-09-21 13:27:50 +02:00
}
async canAddUser(tenantId: string): Promise<{ can_add: boolean; reason?: string; current_count?: number; max_allowed?: number }> {
2025-10-07 07:15:07 +02:00
return apiClient.get(`${this.baseUrl}/subscriptions/${tenantId}/can-add-user`);
2025-09-21 13:27:50 +02:00
}
async hasFeature(tenantId: string, featureName: string): Promise<{ has_feature: boolean; feature_value?: any; plan?: string; reason?: string }> {
2025-10-07 07:15:07 +02:00
return apiClient.get(`${this.baseUrl}/subscriptions/${tenantId}/features/${featureName}`);
2025-09-20 08:59:12 +02:00
}
formatPrice(amount: number): string {
return new Intl.NumberFormat('es-ES', {
style: 'currency',
currency: 'EUR',
minimumFractionDigits: 0,
maximumFractionDigits: 2
}).format(amount);
}
2025-09-23 22:11:34 +02:00
/**
* Fetch available subscription plans from the backend
*/
async fetchAvailablePlans(): Promise<AvailablePlans> {
const now = Date.now();
// Return cached data if it's still valid
if (cachedPlans && lastFetchTime && (now - lastFetchTime) < CACHE_DURATION) {
return cachedPlans;
}
try {
2025-09-25 14:30:47 +02:00
const plans = await apiClient.get<AvailablePlans>('/plans');
2025-09-23 22:11:34 +02:00
cachedPlans = plans;
lastFetchTime = now;
return plans;
} catch (error) {
console.error('Failed to fetch subscription plans:', error);
throw error;
}
}
/**
* Get plan display information
*/
async getPlanDisplayInfo(planKey: string) {
try {
const plans = await this.fetchAvailablePlans();
const plan = plans.plans[planKey];
if (plan) {
return {
name: plan.name,
color: this.getPlanColor(planKey),
description: plan.description,
monthlyPrice: plan.monthly_price
};
}
return { name: 'Desconocido', color: 'gray', description: '', monthlyPrice: 0 };
} catch (error) {
console.error('Failed to get plan display info:', error);
return { name: 'Desconocido', color: 'gray', description: '', monthlyPrice: 0 };
}
}
/**
* Get plan color based on plan key
*/
getPlanColor(planKey: string): string {
switch (planKey) {
case SUBSCRIPTION_PLANS.STARTER:
return 'blue';
case SUBSCRIPTION_PLANS.PROFESSIONAL:
return 'purple';
case SUBSCRIPTION_PLANS.ENTERPRISE:
return 'amber';
default:
return 'gray';
}
}
/**
* Check if a plan meets minimum requirements
*/
doesPlanMeetMinimum(plan: SubscriptionPlanKey, minimumRequired: SubscriptionPlanKey): boolean {
return PLAN_HIERARCHY[plan] >= PLAN_HIERARCHY[minimumRequired];
}
/**
* Get analytics level for a plan
*/
getAnalyticsLevelForPlan(plan: SubscriptionPlanKey): AnalyticsLevel {
return PLAN_TO_ANALYTICS_LEVEL[plan] || ANALYTICS_LEVELS.NONE;
}
/**
* Check if analytics level meets minimum requirements
*/
doesAnalyticsLevelMeetMinimum(level: AnalyticsLevel, minimumRequired: AnalyticsLevel): boolean {
return ANALYTICS_HIERARCHY[level] >= ANALYTICS_HIERARCHY[minimumRequired];
}
/**
* Get plan features
*/
async getPlanFeatures(planKey: string) {
try {
const plans = await this.fetchAvailablePlans();
const plan = plans.plans[planKey];
if (plan) {
return plan.features || {};
}
return {};
} catch (error) {
console.error('Failed to get plan features:', error);
return {};
}
}
/**
* Check if a plan has a specific feature
*/
async planHasFeature(planKey: string, featureName: string) {
try {
const features = await this.getPlanFeatures(planKey);
return featureName in features;
} catch (error) {
console.error('Failed to check plan feature:', error);
return false;
}
2025-09-20 08:59:12 +02:00
}
}
export const subscriptionService = new SubscriptionService();