add optinal apps

This commit is contained in:
keyhan
2026-04-23 15:26:49 +03:30
parent f481a57d8f
commit 38748b0827
16 changed files with 3091 additions and 144 deletions
+116 -3
View File
@@ -10,7 +10,9 @@ import {
CreateServicePlanDto,
UpdateServicePlanDto,
CalculateCostDto,
UpgradeResourcesDto,
} from './dto/billing.dto';
import { Application } from '../applications/entities/application.entity';
@Injectable()
export class BillingService {
@@ -133,9 +135,14 @@ export class BillingService {
// Parse resource values
const cpuCores = this.parseCpuToCores(dto.cpuLimit);
const memoryGb = this.parseMemoryToGb(dto.memoryLimit);
const storageGb = dto.dbStorageSize ? parseFloat(dto.dbStorageSize.replace('Gi', '')) || 0 : 0;
const dbStorageGb = dto.dbStorageSize ? parseFloat(dto.dbStorageSize.replace('Gi', '')) || 0 : 0;
const appStorageGb = dto.appStorageSize ? parseFloat(dto.appStorageSize.replace('Gi', '')) || 0 : 0;
const totalStorageGb = dbStorageGb + appStorageGb;
const hasDatabase = dto.databaseType !== 'none';
const replicas = dto.replicas || 1;
const hasRedis = dto.enableRedis || false;
const hasRabbitmq = dto.enableRabbitmq || false;
const hasElasticsearch = dto.enableElasticsearch || false;
const breakdown: { label: string; hourly: number; monthly: number; yearly: number }[] = [];
let totalBase = 0;
@@ -158,13 +165,25 @@ export class BillingService {
label = `Memory (${(memoryGb * replicas).toFixed(2)} GB)`;
break;
case PricingResourceType.STORAGE_PER_GB:
cost = storageGb * Number(rule.unitPrice);
label = `Storage (${storageGb} GB)`;
cost = totalStorageGb * Number(rule.unitPrice);
label = `Storage (${totalStorageGb} GB)`;
break;
case PricingResourceType.DATABASE_ADDON:
cost = hasDatabase ? Number(rule.unitPrice) : 0;
label = 'Database addon';
break;
case PricingResourceType.REDIS_ADDON:
cost = hasRedis ? Number(rule.unitPrice) : 0;
label = 'Redis addon';
break;
case PricingResourceType.RABBITMQ_ADDON:
cost = hasRabbitmq ? Number(rule.unitPrice) : 0;
label = 'RabbitMQ addon';
break;
case PricingResourceType.ELASTICSEARCH_ADDON:
cost = hasElasticsearch ? Number(rule.unitPrice) : 0;
label = 'Elasticsearch addon';
break;
}
if (cost > 0) {
@@ -300,6 +319,7 @@ export class BillingService {
memoryLimit: string;
replicas: number;
dbStorageSize?: string;
appStorageSize?: string;
}): Promise<{ hourly: number; monthly: number; yearly: number }> {
const result = await this.calculateCost({
runtime: app.runtime,
@@ -308,7 +328,100 @@ export class BillingService {
memoryLimit: app.memoryLimit,
replicas: app.replicas,
dbStorageSize: app.dbStorageSize,
appStorageSize: app.appStorageSize,
});
return { hourly: result.hourly, monthly: result.monthly, yearly: result.yearly };
}
// ─── Renewal Cost Calculation ─────────────────────────────────────
/**
* Calculate renewal cost for an application.
* Returns cost for each billing cycle based on current app config.
*/
async calculateRenewalCost(app: Application): Promise<{
hourly: number;
monthly: number;
yearly: number;
currentCycle?: BillingCycle;
currentCycleCost?: number;
}> {
const costs = await this.calculateCostForApp(app);
let currentCycleCost: number | undefined;
if (app.billingCycle) {
currentCycleCost = app.billingCycle === BillingCycle.HOURLY ? costs.hourly
: app.billingCycle === BillingCycle.MONTHLY ? costs.monthly
: costs.yearly;
}
return {
...costs,
currentCycle: app.billingCycle,
currentCycleCost,
};
}
// ─── Resource Upgrade Cost Calculation ────────────────────────────
/**
* Calculate the cost difference for a resource upgrade.
* Returns the additional cost per billing cycle.
*/
async calculateUpgradeCost(
app: Application,
newResources: UpgradeResourcesDto,
): Promise<{
currentCost: { hourly: number; monthly: number; yearly: number };
newCost: { hourly: number; monthly: number; yearly: number };
difference: { hourly: number; monthly: number; yearly: number };
proratedAmount: number;
remainingHours: number;
billingCycle: BillingCycle | null;
}> {
// Current cost
const currentCost = await this.calculateCostForApp(app);
// New cost with upgraded resources
const newCost = await this.calculateCost({
runtime: app.runtime,
databaseType: app.databaseType,
cpuLimit: newResources.cpuLimit || app.cpuLimit,
memoryLimit: newResources.memoryLimit || app.memoryLimit,
replicas: newResources.replicas ?? app.replicas,
dbStorageSize: newResources.dbStorageSize || app.dbStorageSize,
appStorageSize: newResources.appStorageSize || app.appStorageSize,
});
// Difference
const difference = {
hourly: newCost.hourly - currentCost.hourly,
monthly: newCost.monthly - currentCost.monthly,
yearly: newCost.yearly - currentCost.yearly,
};
// Calculate prorated amount based on remaining time in billing period
let proratedAmount = 0;
let remainingHours = 0;
if (app.planExpiresAt && app.billingCycle) {
const now = new Date();
const expiresAt = new Date(app.planExpiresAt);
remainingHours = Math.max(0, (expiresAt.getTime() - now.getTime()) / (1000 * 60 * 60));
// Only charge difference if upgrading (not downgrading)
if (difference.hourly > 0) {
proratedAmount = Math.ceil(difference.hourly * remainingHours);
}
}
return {
currentCost,
newCost,
difference,
proratedAmount,
remainingHours: Math.round(remainingHours),
billingCycle: app.billingCycle,
};
}
}