From 1d2d99cdc4a37964ec48837c2ee0cbb82e67bb8b Mon Sep 17 00:00:00 2001 From: Joachim Ansorg Date: Mon, 8 Jul 2024 11:16:20 +0200 Subject: [PATCH] chore: refactor revenue tracker --- .../data/trackers/RecurringRevenueTracker.kt | 54 +++++++++---------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/marketplace-data/src/main/kotlin/dev/ja/marketplace/data/trackers/RecurringRevenueTracker.kt b/marketplace-data/src/main/kotlin/dev/ja/marketplace/data/trackers/RecurringRevenueTracker.kt index 1223d24..cdd6f19 100644 --- a/marketplace-data/src/main/kotlin/dev/ja/marketplace/data/trackers/RecurringRevenueTracker.kt +++ b/marketplace-data/src/main/kotlin/dev/ja/marketplace/data/trackers/RecurringRevenueTracker.kt @@ -15,27 +15,21 @@ import java.util.* * Tracks recurring revenue for a given time period, e.g. one month or one year. */ abstract class RecurringRevenueTracker( - protected val dateRange: YearMonthDayRange, + private val dateRange: YearMonthDayRange, protected val pluginInfo: MarketplacePluginInfo, - protected val continuityTracker: ContinuityDiscountTracker, + private val continuityTracker: ContinuityDiscountTracker, ) { private val latestSales = TreeMap() - protected abstract fun dateRangeSubscriptionPrice(license: LicenseInfo): Amount + protected abstract fun dateRangeSubscriptionPrice(basePrice: Amount, licensePeriod: LicensePeriod): Amount - /** - * @return The expected continuity discount for the next month or year. - */ - protected abstract fun nextContinuityDiscountFactor(license: LicenseInfo): Double + protected abstract fun nextContinuityCheckDate(date: YearMonthDay): YearMonthDay fun processLicenseSale(licenseInfo: LicenseInfo) { if (!licenseInfo.isPaidLicense) { return } - // track all, not just those in the filtered revenue range - continuityTracker.process(licenseInfo) - // only keep the latest valid sale of a license if (isValid(licenseInfo)) { latestSales.merge(licenseInfo.id, licenseInfo) { old, new -> @@ -51,7 +45,12 @@ abstract class RecurringRevenueTracker( var result = Amount(0) for ((_, license) in latestSales) { - val rangeBasePrice = dateRangeSubscriptionPrice(license) + val basePrice = when (license.sale.customer.type) { + CustomerType.Personal -> pluginInfo.individualPrice + CustomerType.Organization -> pluginInfo.businessPrice + } + + val rangeBasePrice = dateRangeSubscriptionPrice(basePrice, license.sale.licensePeriod) val continuityFactor = nextContinuityDiscountFactor(license) val discounts = otherDiscountsFactor(license) * continuityFactor @@ -61,6 +60,13 @@ abstract class RecurringRevenueTracker( return RecurringRevenue(dateRange, result) } + /** + * @return The expected continuity discount for the next month or year. + */ + private fun nextContinuityDiscountFactor(license: LicenseInfo): Double { + return continuityTracker.nextContinuityFactor(license.id, nextContinuityCheckDate(license.validity.start)) + } + private fun isValid(license: LicenseInfo): Boolean { // Only take licenses which are valid at the end of the date range, e.g. at the end of a month. // Licenses only valid at the start but expiring in the middle of a month do not contribute to recurring revenue. @@ -89,17 +95,12 @@ class MonthlyRecurringRevenueTracker( // annual subscription is 12 * monthly subscription price, calculated for a single month private val annualToMonthlyFactor = BigDecimal.valueOf(10.0 / 12.0) - override fun nextContinuityDiscountFactor(license: LicenseInfo): Double { - return continuityTracker.nextContinuityFactor(license.id, license.validity.start.add(0, 1, 0)) + override fun nextContinuityCheckDate(date: YearMonthDay): YearMonthDay { + return date.add(0, 1, 0) } - override fun dateRangeSubscriptionPrice(license: LicenseInfo): Amount { - val basePrice = when (license.sale.customer.type) { - CustomerType.Personal -> pluginInfo.individualPrice - CustomerType.Organization -> pluginInfo.businessPrice - } - - return when (license.sale.licensePeriod) { + override fun dateRangeSubscriptionPrice(basePrice: Amount, licensePeriod: LicensePeriod): Amount { + return when (licensePeriod) { LicensePeriod.Monthly -> basePrice LicensePeriod.Annual -> basePrice * annualToMonthlyFactor } @@ -117,17 +118,12 @@ class AnnualRecurringRevenueTracker( // monthly subscription is paid 12 times per year private val monthlyToAnnualFactor = BigDecimal.valueOf(12) - override fun nextContinuityDiscountFactor(license: LicenseInfo): Double { - return continuityTracker.nextContinuityFactor(license.id, license.validity.start.add(1, 0, 0)) + override fun nextContinuityCheckDate(date: YearMonthDay): YearMonthDay { + return date.add(1, 0, 0) } - override fun dateRangeSubscriptionPrice(license: LicenseInfo): Amount { - val basePrice = when (license.sale.customer.type) { - CustomerType.Personal -> pluginInfo.individualPrice - CustomerType.Organization -> pluginInfo.businessPrice - } - - return when (license.sale.licensePeriod) { + override fun dateRangeSubscriptionPrice(basePrice: Amount, licensePeriod: LicensePeriod): Amount { + return when (licensePeriod) { LicensePeriod.Monthly -> basePrice * monthlyToAnnualFactor LicensePeriod.Annual -> basePrice * annualFactor }