Skip to content

Commit

Permalink
Reinstate invoice date with holiday-stop credit
Browse files Browse the repository at this point in the history
  • Loading branch information
kelvin-chappell committed Oct 3, 2019
1 parent 07238ca commit dcd5097
Show file tree
Hide file tree
Showing 12 changed files with 53 additions and 39 deletions.
Expand Up @@ -2,11 +2,10 @@ package com.gu.holiday_stops

import java.io.{InputStream, OutputStream}
import java.time.LocalDate

import cats.syntax.either._
import com.amazonaws.services.lambda.runtime.Context
import com.gu.effects.{GetFromS3, RawEffects}
import com.gu.holiday_stops.subscription.{StoppedProduct, Subscription}
import com.gu.holiday_stops.subscription.{HolidayStopCredit, StoppedProduct, Subscription}
import com.gu.salesforce.SalesforceClient
import com.gu.salesforce.holiday_stops.SalesforceHolidayStopRequest._
import com.gu.salesforce.holiday_stops.SalesforceHolidayStopRequestsDetail.{HolidayStopRequestId, Product, ProductName, StoppedPublicationDate, SubscriptionName}
Expand Down Expand Up @@ -173,9 +172,8 @@ object Handler extends Logging {
// unfortunately necessary due to GW N-for-N requiring stoppedPublicationDate to calculate correct credit estimation
PotentialHolidayStop(
stoppedPublicationDate,
optionalSubscription flatMap { sub =>
StoppedProduct(sub, StoppedPublicationDate(stoppedPublicationDate)).map(_.credit).toOption
}
optionalSubscription.flatMap(sub =>
creditCalculator(stoppedPublicationDate, sub).toOption.map(HolidayStopCredit(_)))
)
}
} yield ApiGatewayResponse("200", PotentialHolidayStopsResponse(potentialHolidayStops))).apiResponse
Expand All @@ -198,7 +196,7 @@ object Handler extends Logging {
queryParams match {
case PotentialHolidayStopsQueryParams(startDate, endDate, _, Some(productType), Some(productRatePlanName)) =>
ActionCalculator
.publicationDatesToBeStopped(startDate, endDate, Product(productType, productRatePlanName))
.publicationDatesToBeStopped(startDate, endDate, Product(productType, (productRatePlanName)))
.toApiGatewayOp(s"calculating publication dates")

case PotentialHolidayStopsQueryParams(startDate, endDate, _, _, _) =>
Expand Down Expand Up @@ -313,6 +311,6 @@ object Handler extends Logging {

def credit(config: Config)(stoppedPublicationDate: LocalDate, subscription: Subscription): Either[ZuoraHolidayError, Double] =
StoppedProduct(subscription, StoppedPublicationDate(stoppedPublicationDate))
.map(_.credit.amount)
.map(_.credit)
.leftMap(e => ZuoraHolidayError(s"Failed to calculate holiday stop credits because $e"))
}
Expand Up @@ -177,8 +177,8 @@ class HandlerTest extends FlatSpec with Matchers {
response should equal(
PotentialHolidayStopsResponse(
List(
PotentialHolidayStop(LocalDate.of(2019, 1, 4), Some(HolidayStopCredit(-2.89, LocalDate.parse("2019-04-01")))),
PotentialHolidayStop(LocalDate.of(2019, 1, 11), Some(HolidayStopCredit(-2.89, LocalDate.parse("2019-04-01")))),
PotentialHolidayStop(LocalDate.of(2019, 1, 4), Some(HolidayStopCredit(-2.89))),
PotentialHolidayStop(LocalDate.of(2019, 1, 11), Some(HolidayStopCredit(-2.89))),
)
)
)
Expand Down
Expand Up @@ -78,9 +78,10 @@ object Processor {
subscription <- getSubscription(stop.subscriptionName)
stoppedProduct <- StoppedProduct(subscription, StoppedPublicationDate(stop.stoppedPublicationDate))
_ <- if (subscription.autoRenew) Right(()) else Left(ZuoraHolidayError("Cannot currently process non-auto-renewing subscription"))
nextInvoiceStartDate = stoppedProduct.nextBillingPeriodStartDate
maybeExtendedTerm = ExtendedTerm(nextInvoiceStartDate, subscription)
holidayCredit = stoppedProduct.credit
maybeExtendedTerm = ExtendedTerm(holidayCredit.invoiceDate, subscription)
holidayCreditUpdate <- HolidayCreditUpdate(config.holidayCreditProduct, subscription, stop.stoppedPublicationDate, maybeExtendedTerm, holidayCredit)
holidayCreditUpdate <- HolidayCreditUpdate(config.holidayCreditProduct, subscription, stop.stoppedPublicationDate, nextInvoiceStartDate, maybeExtendedTerm, holidayCredit)
_ <- if (subscription.hasHolidayStop(stop)) Right(()) else updateSubscription(subscription, holidayCreditUpdate)
updatedSubscription <- getSubscription(stop.subscriptionName)
addedCharge <- updatedSubscription.ratePlanCharge(stop).toRight(ZuoraHolidayError(s"Failed to write holiday stop to Zuora: $stop"))
Expand Down
Expand Up @@ -2,6 +2,7 @@ package com.gu.holidaystopprocessor

import java.time.LocalDate

import com.gu.holiday_stops
import com.gu.holiday_stops._
import com.gu.holiday_stops.subscription._
import com.gu.salesforce.holiday_stops.SalesforceHolidayStopRequestsDetail.StoppedPublicationDate
Expand All @@ -21,13 +22,15 @@ class HolidayCreditUpdateTest extends FlatSpec with Matchers with EitherValues {
chargedThroughDate = Some(chargedThroughDate)
)
val currentGuardianWeeklySubscription = GuardianWeeklySubscription(subscription, stoppedPublicationDate).right.value
val nextInvoiceStartDate = currentGuardianWeeklySubscription.nextBillingPeriodStartDate
val maybeExtendedTerm = ExtendedTerm(nextInvoiceStartDate, subscription)
val holidayCredit = currentGuardianWeeklySubscription.credit
val maybeExtendedTerm = ExtendedTerm(holidayCredit.invoiceDate, subscription)

val update = HolidayCreditUpdate(
Fixtures.config.holidayCreditProduct,
subscription = subscription,
stoppedPublicationDate = LocalDate.of(2019, 5, 18),
nextInvoiceStartDate = nextInvoiceStartDate,
maybeExtendedTerm = maybeExtendedTerm,
holidayCredit
)
Expand Down Expand Up @@ -73,12 +76,14 @@ class HolidayCreditUpdateTest extends FlatSpec with Matchers with EitherValues {
chargedThroughDate = Some(LocalDate.of(2020, 8, 2))
)
val currentGuardianWeeklySubscription = GuardianWeeklySubscription(subscription, stoppedPublicationDate).right.value
val nextInvoiceStartDate = currentGuardianWeeklySubscription.nextBillingPeriodStartDate
val maybeExtendedTerm = ExtendedTerm(nextInvoiceStartDate, subscription)
val holidayCredit = currentGuardianWeeklySubscription.credit
val maybeExtendedTerm = ExtendedTerm(holidayCredit.invoiceDate, subscription)
val update = HolidayCreditUpdate(
Fixtures.config.holidayCreditProduct,
subscription = subscription,
stoppedPublicationDate = LocalDate.of(2019, 8, 6),
nextInvoiceStartDate = nextInvoiceStartDate,
maybeExtendedTerm = maybeExtendedTerm,
holidayCredit
)
Expand Down Expand Up @@ -111,12 +116,14 @@ class HolidayCreditUpdateTest extends FlatSpec with Matchers with EitherValues {
chargedThroughDate = Some(LocalDate.of(2020, 7, 23))
)
val currentGuardianWeeklySubscription = GuardianWeeklySubscription(subscription, stoppedPublicationDate).right.value
val nextInvoiceStartDate = currentGuardianWeeklySubscription.nextBillingPeriodStartDate
val maybeExtendedTerm = ExtendedTerm(nextInvoiceStartDate, subscription)
val holidayCredit = currentGuardianWeeklySubscription.credit
val maybeExtendedTerm = ExtendedTerm(holidayCredit.invoiceDate, subscription)
val update = HolidayCreditUpdate(
Fixtures.config.holidayCreditProduct,
subscription = subscription,
stoppedPublicationDate = LocalDate.of(2019, 8, 6),
nextInvoiceStartDate = nextInvoiceStartDate,
maybeExtendedTerm = maybeExtendedTerm,
holidayCredit
)
Expand Down
Expand Up @@ -17,6 +17,6 @@ class SundayVoucherNextBillingPeriodStartDateSpec extends FlatSpec with Matchers
val stoppedProduct = StoppedProduct(subscription, StoppedPublicationDate(LocalDate.parse("2019-10-27"))).right.value
stoppedProduct shouldBe a[VoucherSubscription]
stoppedProduct should matchPattern { case VoucherSubscription(_, _, _, _, _, VoucherDayOfWeek.Sunday) => }
stoppedProduct.credit.invoiceDate should be(LocalDate.of(2019, 11, 6))
stoppedProduct.nextBillingPeriodStartDate should be(LocalDate.of(2019, 11, 6))
}
}
Expand Up @@ -21,8 +21,9 @@ object HolidayCreditUpdate {
holidayCreditProduct: HolidayCreditProduct,
subscription: Subscription,
stoppedPublicationDate: LocalDate,
nextInvoiceStartDate: LocalDate,
maybeExtendedTerm: Option[ExtendedTerm],
holidayCredit: HolidayStopCredit
holidayCredit: Double
): Either[ZuoraHolidayError, HolidayCreditUpdate] = {
Right(
HolidayCreditUpdate(
Expand All @@ -31,15 +32,15 @@ object HolidayCreditUpdate {
List(
Add(
productRatePlanId = holidayCreditProduct.productRatePlanId,
contractEffectiveDate = holidayCredit.invoiceDate,
customerAcceptanceDate = holidayCredit.invoiceDate,
serviceActivationDate = holidayCredit.invoiceDate,
contractEffectiveDate = nextInvoiceStartDate,
customerAcceptanceDate = nextInvoiceStartDate,
serviceActivationDate = nextInvoiceStartDate,
chargeOverrides = List(
ChargeOverride(
productRatePlanChargeId = holidayCreditProduct.productRatePlanChargeId,
HolidayStart__c = stoppedPublicationDate,
HolidayEnd__c = stoppedPublicationDate,
price = holidayCredit.amount
price = holidayCredit
)
)
)
Expand Down
Expand Up @@ -3,3 +3,10 @@ package com.gu.holiday_stops.subscription
import java.time.LocalDate

case class HolidayStopCredit(amount: Double, invoiceDate: LocalDate)

object HolidayStopCredit {

// TODO: use genuine invoice date
def apply(amount: Double): HolidayStopCredit =
HolidayStopCredit(amount, invoiceDate = LocalDate.of(1970, 1, 1))
}
Expand Up @@ -2,10 +2,10 @@ package com.gu.holiday_stops.subscription

import java.time.LocalDate

import acyclic.skipped
import cats.syntax.either._
import com.gu.holiday_stops.{ZuoraHolidayError, ZuoraHolidayResponse}
import com.gu.salesforce.holiday_stops.SalesforceHolidayStopRequestsDetail.StoppedPublicationDate
import cats.syntax.either._
import acyclic.skipped
import scala.math.BigDecimal.RoundingMode

/**
Expand Down Expand Up @@ -36,7 +36,7 @@ abstract class StoppedProduct(
val billingPeriod: String,
val invoicedPeriod: CurrentInvoicedPeriod,
) {
private def creditAmount: Double = {
def credit: Double = {
def roundUp(d: Double): Double = BigDecimal(d).setScale(2, RoundingMode.UP).toDouble
val recurringPrice = price
val numPublicationsInPeriod = billingPeriodToApproxWeekCount(billingPeriod)
Expand Down Expand Up @@ -65,7 +65,7 @@ abstract class StoppedProduct(
* If the holiday falls within N-for-N then credit should be applied on the first regular invoice, not the next billing
* period of GW regular plan.
*/
private def nextBillingPeriodStartDate: LocalDate = invoicedPeriod.endDateExcluding
def nextBillingPeriodStartDate: LocalDate = invoicedPeriod.endDateExcluding

private def billingPeriodToApproxWeekCount(billingPeriod: String): Int =
billingPeriod match {
Expand All @@ -76,8 +76,6 @@ abstract class StoppedProduct(
case "Specific_Weeks" => 6 // FIXME: When we have others than 6-for-6
case _ => throw new RuntimeException(s"Failed to convert billing period to weeks because unknown period: $billingPeriod") // FIXME: Either
}

def credit = HolidayStopCredit(amount = creditAmount, invoiceDate = nextBillingPeriodStartDate)
}

object StoppedProduct {
Expand Down
Expand Up @@ -2,7 +2,7 @@ package com.gu.holiday_stops

import java.time.LocalDate

import com.gu.holiday_stops.subscription.{GuardianWeeklySubscription, HolidayStopCredit, RatePlan}
import com.gu.holiday_stops.subscription.{GuardianWeeklySubscription, RatePlan}
import com.gu.salesforce.holiday_stops.SalesforceHolidayStopRequestsDetail.StoppedPublicationDate
import org.scalatest.{EitherValues, FlatSpec, Matchers}

Expand All @@ -16,7 +16,7 @@ class HolidayCreditTest extends FlatSpec with Matchers with EitherValues {
val subscription = Fixtures.mkSubscription().copy(ratePlans = ratePlans)
val currentGuardianWeeklySubscription = GuardianWeeklySubscription(subscription, stoppedPublicationDate)
val credit = currentGuardianWeeklySubscription.right.value.credit
credit shouldBe HolidayStopCredit(-2.31, LocalDate.parse("2019-09-02"))
credit shouldBe -2.31
}

it should "be correct for another quarterly billing period" in {
Expand All @@ -25,7 +25,7 @@ class HolidayCreditTest extends FlatSpec with Matchers with EitherValues {
val subscription = Fixtures.mkSubscription().copy(ratePlans = ratePlans)
val currentGuardianWeeklySubscription = GuardianWeeklySubscription(subscription, stoppedPublicationDate)
val credit = currentGuardianWeeklySubscription.right.value.credit
credit shouldBe HolidayStopCredit(-2.89, LocalDate.parse("2019-09-02"))
credit shouldBe -2.89
}

it should "be correct for an annual billing period" in {
Expand All @@ -34,6 +34,6 @@ class HolidayCreditTest extends FlatSpec with Matchers with EitherValues {
val subscription = Fixtures.mkSubscription().copy(ratePlans = ratePlans)
val currentGuardianWeeklySubscription = GuardianWeeklySubscription(subscription, stoppedPublicationDate)
val credit = currentGuardianWeeklySubscription.right.value.credit
credit shouldBe HolidayStopCredit(-2.31, LocalDate.parse("2019-09-02"))
credit shouldBe -2.31
}
}
Expand Up @@ -2,6 +2,7 @@ package com.gu.holiday_stops.subscription

import java.time.LocalDate

import com.gu.holiday_stops.Fixtures
import com.gu.salesforce.holiday_stops.SalesforceHolidayStopRequestsDetail.StoppedPublicationDate
import io.circe.generic.auto._
import io.circe.parser.decode
Expand All @@ -14,41 +15,41 @@ class CreditCalculatorSpec extends FlatSpec with Matchers with EitherValues {
checkCreditCalculation(
zuoraSubscriptionData = "SundayVoucherSubscription.json",
stopDate = LocalDate.of(2019, 11, 3),
expectedCredit = HolidayStopCredit(-2.70, LocalDate.parse("2019-11-06"))
expectedCredit = -2.70
)
}
it should "calculate credit for guardian weekly in 6 for 6 period" in {
checkCreditCalculation(
zuoraSubscriptionData = "GuardianWeeklyWith6For6.json",
stopDate = LocalDate.of(2019, 11, 8),
expectedCredit = HolidayStopCredit(-1.00, LocalDate.parse("2019-11-15"))
expectedCredit = -1
)
}
it should "calculate credit for guardian weekly in 'normal' period" in {
checkCreditCalculation(
zuoraSubscriptionData = "GuardianWeeklyWith6For6.json",
stopDate = LocalDate.of(2019, 11, 15),
expectedCredit = HolidayStopCredit(-2.89, LocalDate.parse("2020-02-15"))
expectedCredit = -2.89
)
}

it should "calculate credit for weekend vouchers Saturday issue" in {
checkCreditCalculation(
zuoraSubscriptionData = "WeekendVoucherSubscription.json",
stopDate = LocalDate.of(2019, 11, 16),
expectedCredit = HolidayStopCredit(-2.64, LocalDate.parse("2019-11-26"))
expectedCredit = -2.64
)
}

it should "calculate credit for weekend vouchers for a sunday issue" in {
checkCreditCalculation(
zuoraSubscriptionData = "WeekendVoucherSubscription.json",
stopDate = LocalDate.of(2019, 11, 17),
expectedCredit = HolidayStopCredit(-2.55, LocalDate.parse("2019-11-26"))
expectedCredit = -2.55
)
}

private def checkCreditCalculation(zuoraSubscriptionData: String, stopDate: LocalDate, expectedCredit: HolidayStopCredit) = {
private def checkCreditCalculation(zuoraSubscriptionData: String, stopDate: LocalDate, expectedCredit: Double) = {
val subscriptionRaw = Source.fromResource(zuoraSubscriptionData).mkString
val subscription = decode[Subscription](subscriptionRaw).getOrElse(fail(s"Could not decode $zuoraSubscriptionData"))
val stoppedProduct = StoppedProduct(subscription, StoppedPublicationDate(stopDate)).right.value
Expand Down
Expand Up @@ -32,12 +32,12 @@ object GuardianWeeklyHolidayCreditSpec extends Properties("HolidayCreditAmount")
property("should be negative") = forAll(ratePlanChargeGen) { charge: RatePlanCharge =>
val ratePlans = List(RatePlan("Guardian Weekly - Domestic", List(charge), "", ""))
val currentGuardianWeeklySubscription = GuardianWeeklySubscription(subscription.copy(ratePlans = ratePlans), StoppedPublicationDate(chargedThroughDate.minusDays(1)))
currentGuardianWeeklySubscription.right.value.credit.amount < 0
currentGuardianWeeklySubscription.right.value.credit < 0
}

property("should never be overwhelmingly negative") = forAll(ratePlanChargeGen) { charge: RatePlanCharge =>
val ratePlans = List(RatePlan("Guardian Weekly - Domestic", List(charge), "", ""))
val currentGuardianWeeklySubscription = GuardianWeeklySubscription(subscription.copy(ratePlans = ratePlans), StoppedPublicationDate(chargedThroughDate.minusDays(1)))
currentGuardianWeeklySubscription.right.value.credit.amount > -charge.price
currentGuardianWeeklySubscription.right.value.credit > -charge.price
}
}
Expand Up @@ -16,7 +16,8 @@ class StoppedPublicationDateOutsideInvoiceSpec extends FlatSpec with Matchers wi
val subscriptionRaw = Source.fromResource("StoppedPublicationDateOutsideInvoice.json").mkString
val subscription = decode[Subscription](subscriptionRaw).getOrElse(fail("Could not decode GuardianWeeklySubscription"))
val guardianWeeklySub = GuardianWeeklySubscription(subscription, StoppedPublicationDate(LocalDate.parse("2019-10-11")))
guardianWeeklySub.right.value.credit should be(HolidayStopCredit(amount = -6.16, invoiceDate = chargedThroughDate))
guardianWeeklySub.right.value.nextBillingPeriodStartDate should be(chargedThroughDate)
guardianWeeklySub.right.value.credit should be(-6.16)
}

it should "fail if stoppedPublicationDate is before current invoiced period start date" in {
Expand Down

0 comments on commit dcd5097

Please sign in to comment.