Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better determination of migration rate plans for GW2024 #1026

Merged
merged 4 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,46 @@ object GW2024Migration {
"ROW (USD)" -> BigDecimal(396)
)

/*

Date: 27 March 2024
Author: Pascal

I have downloaded all the Zuora subscriptions using the subscription
numbers from the migration cohort ids and extracted the rate plan names. Found:

GW Oct 18 - Monthly - Domestic
GW Oct 18 - Quarterly - Domestic
GW Oct 18 - Annual - Domestic
GW Oct 18 - Monthly - ROW
GW Oct 18 - Quarterly - ROW
GW Oct 18 - Annual - ROW
Guardian Weekly Quarterly
Guardian Weekly Annual
Guardian Weekly 1 Year

It is not necessary to do this as part of preparing and writing the code for a migration,
but doing so gives us an exact list of the rate plan names that we are looking for
and this simplifies the extraction of the rate plan that is going to be upgraded for
each subscription.
*/

val migrationRatePlanNames: List[String] = List(
"GW Oct 18 - Monthly - Domestic",
"GW Oct 18 - Quarterly - Domestic",
"GW Oct 18 - Annual - Domestic",
"GW Oct 18 - Monthly - ROW",
"GW Oct 18 - Quarterly - ROW",
"GW Oct 18 - Annual - ROW",
"Guardian Weekly Quarterly",
"Guardian Weekly Annual",
"Guardian Weekly 1 Year",
)

// ------------------------------------------------
// Data Extraction and Manipulation
// ------------------------------------------------

def getNewPrice(billingPeriod: BillingPeriod, extendedCurrency: Currency): Option[BigDecimal] = {
billingPeriod match {
case Monthly => priceMapMonthlies.get(extendedCurrency)
Expand All @@ -48,33 +88,52 @@ object GW2024Migration {
}
}

// ------------------------------------------------
// Data Extraction and Manipulation
// ------------------------------------------------
// Note about the difference between lastChangeTypeIsAdd and lastChangeTypeIsNotRemove
// Sadly `lastChangeType` is not always defined on all rate plans. The situation is:
// - Not defined -> Active rate plan
// - Defined and value is "Add" -> Active rate plan
// - Defined and value is "Removed" -> Non active rate plan

def lastChangeTypeIsAdd(ratePlan: ZuoraRatePlan): Boolean = {
ratePlan.lastChangeType.contains("Add")
}

def lastChangeTypeIsNotRemove(ratePlan: ZuoraRatePlan): Boolean = {
!ratePlan.lastChangeType.contains("Remove")
}

def subscriptionToMigrationRatePlans(subscription: ZuoraSubscription): List[ZuoraRatePlan] = {
val migrationRatePlanNames = List(
"GW Oct 18 - Monthly - Domestic",
"GW Oct 18 - Quarterly - Domestic",
"GW Oct 18 - Annual - Domestic",
"GW Oct 18 - Monthly - ROW",
"GW Oct 18 - Quarterly - ROW",
"GW Oct 18 - Annual - ROW",
"Guardian Weekly Quarterly",
"Guardian Weekly Annual",
"GW Oct 18 - 1 Year - Domestic",
"Guardian Weekly 1 Year",
)
subscription.ratePlans.filter(rp => migrationRatePlanNames.contains(rp.ratePlanName))
subscription.ratePlans
.filter(rp => lastChangeTypeIsNotRemove(rp))
.filter(rp => migrationRatePlanNames.contains(rp.ratePlanName))
}

def subscriptionToMigrationRatePlan(subscription: ZuoraSubscription): Option[ZuoraRatePlan] = {
subscriptionToMigrationRatePlans(subscription: ZuoraSubscription).headOption
/*
Date: 27 March 2024.
Author: Pascal

In theory, this code is slightly incorrect, because the Zuora data model
doesn't prevent the presence of two active (non Discounts) rate plans with
distinct productRatePlanIds. Because of the way we compute subscriptionToMigrationRatePlans
using actual rate plan names, this would be two active rate plans from the list. This is
unlikely but not impossible.

For argument's sake, if the above special case ever happened, then this code would be selecting
and price rising only the first of such rate plans.

In practice I have downloaded all the subscriptions of this migration and checked that
they all have just one active productRatePlanId. (With that said, and sadly, the
actual JSON blob does sometimes have several instances of the rate rate plan. This is
not a problem though, because the ZuoraSubscriptionUpdate's RemoveZuoraRatePlan refers to
a productRatePlanId and not the rate plan's own id)
*/

subscriptionToMigrationRatePlans(subscription).headOption
}

def subscriptionToCurrency(
subscription: ZuoraSubscription,
account: ZuoraAccount
subscription: ZuoraSubscription
): Option[Currency] = {
for {
ratePlan <- subscriptionToMigrationRatePlan(subscription)
Expand All @@ -84,8 +143,8 @@ object GW2024Migration {

def isROW(subscription: ZuoraSubscription, account: ZuoraAccount): Option[Boolean] = {
for {
ratePlan <- subscriptionToMigrationRatePlan(subscription: ZuoraSubscription)
currency <- ZuoraRatePlan.ratePlanToCurrency(ratePlan: ZuoraRatePlan)
ratePlan <- subscriptionToMigrationRatePlan(subscription)
currency <- ZuoraRatePlan.ratePlanToCurrency(ratePlan)
} yield {
val country = account.soldToContact.country
currency == "USD" && country != "United States"
Expand All @@ -97,8 +156,8 @@ object GW2024Migration {
account: ZuoraAccount
): Option[Currency] = {
for {
currency <- subscriptionToCurrency(subscription, account)
isROW <- isROW(subscription: ZuoraSubscription, account: ZuoraAccount)
currency <- subscriptionToCurrency(subscription)
isROW <- isROW(subscription, account)
} yield if (isROW) "ROW (USD)" else currency
}

Expand Down Expand Up @@ -131,7 +190,7 @@ object GW2024Migration {
account: ZuoraAccount
): Either[AmendmentDataFailure, PriceData] = {
val priceDataOpt = for {
currency <- subscriptionToCurrency(subscription, account)
currency <- subscriptionToCurrency(subscription)
ratePlan <- subscriptionToMigrationRatePlan(subscription)
oldPrice = ZuoraRatePlan.ratePlanToRatePlanPrice(ratePlan)
newPrice <- getNewPrice(subscription, account)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,82 @@ class GW2024MigrationTest extends munit.FunSuite {

// -------------------------------------

test("Rate plan (s) determination is correct (standard)") {
val subscription = Fixtures.subscriptionFromJson("GW2024/standard/subscription.json")
assertEquals(
GW2024Migration.subscriptionToMigrationRatePlans(subscription),
List(
ZuoraRatePlan(
id = "8a128d6988b434050188d210ef897dc2",
productName = "Guardian Weekly - Domestic",
productRatePlanId = "2c92a0fe6619b4b901661aa8e66c1692",
ratePlanName = "GW Oct 18 - Annual - Domestic",
ratePlanCharges = List(
ZuoraRatePlanCharge(
productRatePlanChargeId = "2c92a0fe6619b4b901661aa8e6811695",
name = "GW Oct 18 - Annual - Domestic",
number = "C-02169680",
currency = "USD",
price = Some(BigDecimal(300.0)),
billingPeriod = Some("Annual"),
chargedThroughDate = Some(LocalDate.of(2024, 6, 19)),
processedThroughDate = Some(LocalDate.of(2023, 6, 19)),
specificBillingPeriod = None,
endDateCondition = Some("Subscription_End"),
upToPeriodsType = None,
upToPeriods = None,
billingDay = Some("ChargeTriggerDay"),
triggerEvent = Some("CustomerAcceptance"),
triggerDate = None,
discountPercentage = None,
originalOrderDate = Some(LocalDate.of(2020, 6, 8))
)
),
lastChangeType = Some("Add")
)
)
)
}

test("Rate plan (s) determination is correct (ROW-DomesticRatePlan)") {
val subscription = Fixtures.subscriptionFromJson("GW2024/ROW-DomesticRatePlan/subscription.json")
assertEquals(
GW2024Migration.subscriptionToMigrationRatePlans(subscription),
List(
ZuoraRatePlan(
id = "8a128d0788e11b350188f63ab4995f7a",
productName = "Guardian Weekly - Domestic",
productRatePlanId = "2c92a0fe6619b4b901661aa8e66c1692",
ratePlanName = "GW Oct 18 - Annual - Domestic",
ratePlanCharges = List(
ZuoraRatePlanCharge(
productRatePlanChargeId = "2c92a0fe6619b4b901661aa8e6811695",
name = "GW Oct 18 - Annual - Domestic",
number = "C-02182036",
currency = "USD",
price = Some(BigDecimal(300.0)),
billingPeriod = Some("Annual"),
chargedThroughDate = Some(LocalDate.of(2024, 6, 26)),
processedThroughDate = Some(LocalDate.of(2023, 6, 26)),
specificBillingPeriod = None,
endDateCondition = Some("Subscription_End"),
upToPeriodsType = None,
upToPeriods = None,
billingDay = Some("ChargeTriggerDay"),
triggerEvent = Some("CustomerAcceptance"),
triggerDate = None,
discountPercentage = None,
originalOrderDate = Some(LocalDate.of(2020, 6, 15))
)
),
lastChangeType = None
)
)
)
}

// -------------------------------------

test("Rate plan determination is correct (standard)") {
val subscription = Fixtures.subscriptionFromJson("GW2024/standard/subscription.json")
assertEquals(
Expand Down Expand Up @@ -59,13 +135,54 @@ class GW2024MigrationTest extends munit.FunSuite {
)
}

test("Rate plan determination is correct (ROW-DomesticRatePlan)") {
// This test is used to show the difference between lastChangeTypeIsAdd and lastChangeTypeIsNotRemove
// This is a subscription where `lastChangeType` is not defined, therefore lastChangeTypeIsAdd would not select it
// but lastChangeTypeIsNotRemove would

val subscription = Fixtures.subscriptionFromJson("GW2024/ROW-DomesticRatePlan/subscription.json")
assertEquals(
GW2024Migration.subscriptionToMigrationRatePlan(subscription),
Some(
ZuoraRatePlan(
id = "8a128d0788e11b350188f63ab4995f7a",
productName = "Guardian Weekly - Domestic",
productRatePlanId = "2c92a0fe6619b4b901661aa8e66c1692",
ratePlanName = "GW Oct 18 - Annual - Domestic",
ratePlanCharges = List(
ZuoraRatePlanCharge(
productRatePlanChargeId = "2c92a0fe6619b4b901661aa8e6811695",
name = "GW Oct 18 - Annual - Domestic",
number = "C-02182036",
currency = "USD",
price = Some(BigDecimal(300.0)),
billingPeriod = Some("Annual"),
chargedThroughDate = Some(LocalDate.of(2024, 6, 26)),
processedThroughDate = Some(LocalDate.of(2023, 6, 26)),
specificBillingPeriod = None,
endDateCondition = Some("Subscription_End"),
upToPeriodsType = None,
upToPeriods = None,
billingDay = Some("ChargeTriggerDay"),
triggerEvent = Some("CustomerAcceptance"),
triggerDate = None,
discountPercentage = None,
originalOrderDate = Some(LocalDate.of(2020, 6, 15))
)
),
lastChangeType = None
)
)
)
}

// -------------------------------------

test("subscriptionToMigrationCurrency is correct (standard)") {
val subscription = Fixtures.subscriptionFromJson("GW2024/standard/subscription.json")
val account = Fixtures.accountFromJson("GW2024/standard/account.json")
assertEquals(
GW2024Migration.subscriptionToCurrency(subscription, account),
GW2024Migration.subscriptionToCurrency(subscription),
Some("USD")
)
}
Expand All @@ -74,7 +191,7 @@ class GW2024MigrationTest extends munit.FunSuite {
val subscription = Fixtures.subscriptionFromJson("GW2024/ROW-DomesticRatePlan/subscription.json")
val account = Fixtures.accountFromJson("GW2024/ROW-DomesticRatePlan/account.json")
assertEquals(
GW2024Migration.subscriptionToCurrency(subscription, account),
GW2024Migration.subscriptionToCurrency(subscription),
Some("USD")
)
}
Expand Down