/** * Subscription Plan Value Object * * Represents the different subscription plans available for organizations. * Each plan has a maximum number of licenses, shipment limits, commission rates, * feature flags, and support levels. * * Plans: BRONZE (free), SILVER (249EUR/mo), GOLD (899EUR/mo), PLATINIUM (custom) */ import { PlanFeature, PLAN_FEATURES } from './plan-feature.vo'; export type SubscriptionPlanType = 'BRONZE' | 'SILVER' | 'GOLD' | 'PLATINIUM'; export type SupportLevel = 'none' | 'email' | 'direct' | 'dedicated_kam'; export type StatusBadge = 'none' | 'silver' | 'gold' | 'platinium'; /** * Legacy plan name mapping for backward compatibility during migration. */ const LEGACY_PLAN_MAPPING: Record = { FREE: 'BRONZE', STARTER: 'SILVER', PRO: 'GOLD', ENTERPRISE: 'PLATINIUM', }; interface PlanDetails { readonly name: string; readonly maxLicenses: number; // -1 means unlimited readonly monthlyPriceEur: number; readonly yearlyPriceEur: number; readonly maxShipmentsPerYear: number; // -1 means unlimited readonly commissionRatePercent: number; readonly statusBadge: StatusBadge; readonly supportLevel: SupportLevel; readonly planFeatures: readonly PlanFeature[]; readonly features: readonly string[]; // Human-readable feature descriptions } const PLAN_DETAILS: Record = { BRONZE: { name: 'Bronze', maxLicenses: 1, monthlyPriceEur: 0, yearlyPriceEur: 0, maxShipmentsPerYear: 12, commissionRatePercent: 5, statusBadge: 'none', supportLevel: 'none', planFeatures: PLAN_FEATURES.BRONZE, features: ['1 utilisateur', '12 expéditions par an', 'Recherche de tarifs basique'], }, SILVER: { name: 'Silver', maxLicenses: 5, monthlyPriceEur: 249, yearlyPriceEur: 2739, maxShipmentsPerYear: -1, commissionRatePercent: 3, statusBadge: 'silver', supportLevel: 'email', planFeatures: PLAN_FEATURES.SILVER, features: [ "Jusqu'à 5 utilisateurs", 'Expéditions illimitées', 'Tableau de bord', 'Wiki Maritime', 'Gestion des utilisateurs', 'Import CSV', 'Support par email', ], }, GOLD: { name: 'Gold', maxLicenses: 20, monthlyPriceEur: 899, yearlyPriceEur: 9889, maxShipmentsPerYear: -1, commissionRatePercent: 2, statusBadge: 'gold', supportLevel: 'direct', planFeatures: PLAN_FEATURES.GOLD, features: [ "Jusqu'à 20 utilisateurs", 'Expéditions illimitées', 'Toutes les fonctionnalités Silver', 'Intégration API', 'Assistance commerciale directe', ], }, PLATINIUM: { name: 'Platinium', maxLicenses: -1, // unlimited monthlyPriceEur: 0, // custom pricing yearlyPriceEur: 0, // custom pricing maxShipmentsPerYear: -1, commissionRatePercent: 1, statusBadge: 'platinium', supportLevel: 'dedicated_kam', planFeatures: PLAN_FEATURES.PLATINIUM, features: [ 'Utilisateurs illimités', 'Toutes les fonctionnalités Gold', 'Key Account Manager dédié', 'Interface personnalisable', 'Contrats tarifaires cadre', ], }, }; export class SubscriptionPlan { private constructor(private readonly plan: SubscriptionPlanType) {} static create(plan: SubscriptionPlanType): SubscriptionPlan { if (!PLAN_DETAILS[plan]) { throw new Error(`Invalid subscription plan: ${plan}`); } return new SubscriptionPlan(plan); } /** * Create from string with legacy name support. * Accepts both old (FREE/STARTER/PRO/ENTERPRISE) and new (BRONZE/SILVER/GOLD/PLATINIUM) names. */ static fromString(value: string): SubscriptionPlan { const upperValue = value.toUpperCase(); // Check legacy mapping first const mapped = LEGACY_PLAN_MAPPING[upperValue]; if (mapped) { return new SubscriptionPlan(mapped); } // Try direct match if (PLAN_DETAILS[upperValue as SubscriptionPlanType]) { return new SubscriptionPlan(upperValue as SubscriptionPlanType); } throw new Error(`Invalid subscription plan: ${value}`); } // Named factories static bronze(): SubscriptionPlan { return new SubscriptionPlan('BRONZE'); } static silver(): SubscriptionPlan { return new SubscriptionPlan('SILVER'); } static gold(): SubscriptionPlan { return new SubscriptionPlan('GOLD'); } static platinium(): SubscriptionPlan { return new SubscriptionPlan('PLATINIUM'); } // Legacy aliases static free(): SubscriptionPlan { return SubscriptionPlan.bronze(); } static starter(): SubscriptionPlan { return SubscriptionPlan.silver(); } static pro(): SubscriptionPlan { return SubscriptionPlan.gold(); } static enterprise(): SubscriptionPlan { return SubscriptionPlan.platinium(); } static getAllPlans(): SubscriptionPlan[] { return (['BRONZE', 'SILVER', 'GOLD', 'PLATINIUM'] as SubscriptionPlanType[]).map( p => new SubscriptionPlan(p) ); } // Getters get value(): SubscriptionPlanType { return this.plan; } get name(): string { return PLAN_DETAILS[this.plan].name; } get maxLicenses(): number { return PLAN_DETAILS[this.plan].maxLicenses; } get monthlyPriceEur(): number { return PLAN_DETAILS[this.plan].monthlyPriceEur; } get yearlyPriceEur(): number { return PLAN_DETAILS[this.plan].yearlyPriceEur; } get features(): readonly string[] { return PLAN_DETAILS[this.plan].features; } get maxShipmentsPerYear(): number { return PLAN_DETAILS[this.plan].maxShipmentsPerYear; } get commissionRatePercent(): number { return PLAN_DETAILS[this.plan].commissionRatePercent; } get statusBadge(): StatusBadge { return PLAN_DETAILS[this.plan].statusBadge; } get supportLevel(): SupportLevel { return PLAN_DETAILS[this.plan].supportLevel; } get planFeatures(): readonly PlanFeature[] { return PLAN_DETAILS[this.plan].planFeatures; } hasFeature(feature: PlanFeature): boolean { return this.planFeatures.includes(feature); } isUnlimited(): boolean { return this.maxLicenses === -1; } hasUnlimitedShipments(): boolean { return this.maxShipmentsPerYear === -1; } isPaid(): boolean { return this.plan !== 'BRONZE'; } isFree(): boolean { return this.plan === 'BRONZE'; } isCustomPricing(): boolean { return this.plan === 'PLATINIUM'; } canAccommodateUsers(userCount: number): boolean { if (this.isUnlimited()) return true; return userCount <= this.maxLicenses; } canUpgradeTo(targetPlan: SubscriptionPlan): boolean { const planOrder: SubscriptionPlanType[] = ['BRONZE', 'SILVER', 'GOLD', 'PLATINIUM']; const currentIndex = planOrder.indexOf(this.plan); const targetIndex = planOrder.indexOf(targetPlan.value); return targetIndex > currentIndex; } canDowngradeTo(targetPlan: SubscriptionPlan, currentUserCount: number): boolean { const planOrder: SubscriptionPlanType[] = ['BRONZE', 'SILVER', 'GOLD', 'PLATINIUM']; const currentIndex = planOrder.indexOf(this.plan); const targetIndex = planOrder.indexOf(targetPlan.value); if (targetIndex >= currentIndex) return false; return targetPlan.canAccommodateUsers(currentUserCount); } equals(other: SubscriptionPlan): boolean { return this.plan === other.plan; } toString(): string { return this.plan; } }