Add receipt_document_id FK on maintenance_records, update types/repo/service to support receipt linking on create and return document metadata on GET. Add OCR proxy endpoint POST /api/ocr/extract/maintenance-receipt with tier gating (maintenance.receiptScan) through full chain: routes -> controller -> service -> client. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
161 lines
4.7 KiB
TypeScript
161 lines
4.7 KiB
TypeScript
/**
|
|
* @ai-summary Feature tier configuration and utilities
|
|
* @ai-context Defines feature-to-tier mapping for gating premium features
|
|
*/
|
|
|
|
import { SubscriptionTier } from '../../features/user-profile/domain/user-profile.types';
|
|
|
|
// Tier hierarchy: higher number = higher access level
|
|
export const TIER_LEVELS: Record<SubscriptionTier, number> = {
|
|
free: 0,
|
|
pro: 1,
|
|
enterprise: 2,
|
|
} as const;
|
|
|
|
// Feature configuration interface
|
|
export interface FeatureConfig {
|
|
minTier: SubscriptionTier;
|
|
name: string;
|
|
upgradePrompt: string;
|
|
}
|
|
|
|
// Feature registry - add new gated features here
|
|
export const FEATURE_TIERS: Record<string, FeatureConfig> = {
|
|
'document.scanMaintenanceSchedule': {
|
|
minTier: 'pro',
|
|
name: 'Scan for Maintenance Schedule',
|
|
upgradePrompt: 'Upgrade to Pro to automatically extract maintenance schedules from your vehicle manuals.',
|
|
},
|
|
'vehicle.vinDecode': {
|
|
minTier: 'pro',
|
|
name: 'VIN Decode',
|
|
upgradePrompt: 'Upgrade to Pro to automatically decode VIN and populate vehicle details from the NHTSA database.',
|
|
},
|
|
'fuelLog.receiptScan': {
|
|
minTier: 'pro',
|
|
name: 'Receipt Scan',
|
|
upgradePrompt: 'Upgrade to Pro to scan fuel receipts and auto-fill your fuel log entries.',
|
|
},
|
|
'maintenance.receiptScan': {
|
|
minTier: 'pro',
|
|
name: 'Maintenance Receipt Scan',
|
|
upgradePrompt: 'Upgrade to Pro to scan maintenance receipts and extract service details automatically.',
|
|
},
|
|
} as const;
|
|
|
|
/**
|
|
* Get numeric level for a subscription tier
|
|
*/
|
|
export function getTierLevel(tier: SubscriptionTier): number {
|
|
return TIER_LEVELS[tier] ?? 0;
|
|
}
|
|
|
|
/**
|
|
* Check if a user tier can access a feature
|
|
* Higher tiers inherit access to all lower tier features
|
|
*/
|
|
export function canAccessFeature(userTier: SubscriptionTier, featureKey: string): boolean {
|
|
const feature = FEATURE_TIERS[featureKey];
|
|
if (!feature) {
|
|
// Unknown features are accessible by all (fail open for unlisted features)
|
|
return true;
|
|
}
|
|
return getTierLevel(userTier) >= getTierLevel(feature.minTier);
|
|
}
|
|
|
|
/**
|
|
* Get the minimum required tier for a feature
|
|
* Returns null if feature is not gated
|
|
*/
|
|
export function getRequiredTier(featureKey: string): SubscriptionTier | null {
|
|
const feature = FEATURE_TIERS[featureKey];
|
|
return feature?.minTier ?? null;
|
|
}
|
|
|
|
/**
|
|
* Get full feature configuration
|
|
* Returns undefined if feature is not registered
|
|
*/
|
|
export function getFeatureConfig(featureKey: string): FeatureConfig | undefined {
|
|
return FEATURE_TIERS[featureKey];
|
|
}
|
|
|
|
/**
|
|
* Get all feature configurations (for API endpoint)
|
|
*/
|
|
export function getAllFeatureConfigs(): Record<string, FeatureConfig> {
|
|
return { ...FEATURE_TIERS };
|
|
}
|
|
|
|
// Vehicle limits per tier
|
|
// null indicates unlimited (enterprise tier)
|
|
export const VEHICLE_LIMITS: Record<SubscriptionTier, number | null> = {
|
|
free: 2,
|
|
pro: 5,
|
|
enterprise: null,
|
|
} as const;
|
|
|
|
/**
|
|
* Vehicle limits vary by subscription tier and must be queryable
|
|
* at runtime for both backend enforcement and frontend UI state.
|
|
*
|
|
* @param tier - User's subscription tier
|
|
* @returns Maximum vehicles allowed, or null for unlimited (enterprise tier)
|
|
*/
|
|
export function getVehicleLimit(tier: SubscriptionTier): number | null {
|
|
return VEHICLE_LIMITS[tier] ?? null;
|
|
}
|
|
|
|
/**
|
|
* Check if a user can add another vehicle based on their tier and current count.
|
|
*
|
|
* @param tier - User's subscription tier
|
|
* @param currentCount - Number of vehicles user currently has
|
|
* @returns true if user can add another vehicle, false if at/over limit
|
|
*/
|
|
export function canAddVehicle(tier: SubscriptionTier, currentCount: number): boolean {
|
|
const limit = getVehicleLimit(tier);
|
|
// null limit means unlimited (enterprise)
|
|
if (limit === null) {
|
|
return true;
|
|
}
|
|
return currentCount < limit;
|
|
}
|
|
|
|
/**
|
|
* Vehicle limit configuration with upgrade prompt.
|
|
* Structure supports additional resource types in the future.
|
|
*/
|
|
export interface VehicleLimitConfig {
|
|
limit: number | null;
|
|
tier: SubscriptionTier;
|
|
upgradePrompt: string;
|
|
}
|
|
|
|
/**
|
|
* Get vehicle limit configuration with upgrade prompt for a tier.
|
|
*
|
|
* @param tier - User's subscription tier
|
|
* @returns Configuration with limit and upgrade prompt
|
|
*/
|
|
export function getVehicleLimitConfig(tier: SubscriptionTier): VehicleLimitConfig {
|
|
const limit = getVehicleLimit(tier);
|
|
|
|
const defaultPrompt = 'Upgrade to access additional vehicles.';
|
|
|
|
let upgradePrompt: string;
|
|
if (tier === 'free') {
|
|
upgradePrompt = 'Free tier is limited to 2 vehicles. Upgrade to Pro for up to 5 vehicles, or Enterprise for unlimited.';
|
|
} else if (tier === 'pro') {
|
|
upgradePrompt = 'Pro tier is limited to 5 vehicles. Upgrade to Enterprise for unlimited vehicles.';
|
|
} else {
|
|
upgradePrompt = defaultPrompt;
|
|
}
|
|
|
|
return {
|
|
limit,
|
|
tier,
|
|
upgradePrompt,
|
|
};
|
|
}
|