in membership-common/src/main/scala/com/gu/memsub/subsv2/Subscription.scala [55:94]
def currentPlans(sub: Subscription, date: LocalDate, catalog: Catalog): String \/ NonEmptyList[RatePlan] = {
val currentPlans = sub.ratePlans.sortBy(_.totalChargesMinorUnit).reverse.map { plan =>
val product = plan.product(catalog)
// If the sub hasn't been paid yet but has started we should fast-forward to the date of first payment (free trial)
val dateToCheck = if (sub.contractEffectiveDate <= date && sub.customerAcceptanceDate > date) sub.customerAcceptanceDate else date
val unvalidated = Validation.s[NonEmptyList[DiscardedPlan]](plan)
/*
Note that a Contributor may have future sub.acceptanceDate and plan.startDate values if the user has
updated their payment amount via MMA since starting the contribution. In this case the alreadyStarted assessment
just checks that the sub.startDate is before, or the same as, the date received by this function.
*/
val ensureStarted = unvalidated.ensure(DiscardedPlan(plan, s"hasn't started as of $dateToCheck").wrapNel)(_)
val alreadyStarted =
if (product == Contribution)
ensureStarted(_ => sub.contractEffectiveDate <= date)
else
ensureStarted(_.effectiveStartDate <= dateToCheck)
val contributorPlanCancelled =
alreadyStarted.ensure(DiscardedPlan(plan, "has a contributor plan which has been cancelled or removed").wrapNel)(_)
val paidPlanEnded = alreadyStarted.ensure(DiscardedPlan(plan, "has a paid plan which has ended").wrapNel)(_)
val digipackGiftEnded = alreadyStarted.ensure(DiscardedPlan(plan, "has a digipack gift plan which has ended").wrapNel)(_)
if (product == Product.Contribution)
contributorPlanCancelled(_ => !sub.isCancelled && !plan.lastChangeType.contains("Remove"))
else if (product == Product.Digipack && plan.billingPeriod.toOption.contains(OneTimeChargeBillingPeriod))
digipackGiftEnded(_ => sub.termEndDate >= dateToCheck)
else
paidPlanEnded(_ => {
val inGracePeriodAndNotCancelled = plan.effectiveEndDate == dateToCheck && !sub.isCancelled && !plan.lastChangeType.contains("Remove")
plan.effectiveEndDate > dateToCheck || inGracePeriodAndNotCancelled
})
}
Sequence(
currentPlans.map(
_.leftMap(_.map(discard => s"Discarded ${discard.plan.id.get} because it ${discard.why}").list.toList.mkString("\n")).toDisjunction,
),
)
}