Skip to content

Commit

Permalink
Merge pull request #409 from guardian/sunday-holiday-stops-skeleton
Browse files Browse the repository at this point in the history
Sunday holiday stops skeleton
  • Loading branch information
frj committed Sep 12, 2019
2 parents 8652843 + f4c4cca commit 36262f7
Show file tree
Hide file tree
Showing 19 changed files with 461 additions and 328 deletions.
Expand Up @@ -122,7 +122,7 @@ class HandlerTest extends FlatSpec with Matchers {
HolidayEnd__c = None,
processedThroughDate = Some(endDate.plus(1, ChronoUnit.DAYS).minus(3, ChronoUnit.MONTHS))
)),
Config.guardianWeeklyProductRatePlanIdsDEV.head,
GuardianWeeklyHolidayStopConfig.Dev.productRatePlanIds.head,
""
)
)
Expand Down
@@ -0,0 +1,84 @@
package com.gu.holidaystopprocessor

import java.time.LocalDate

import com.gu.holiday_stops.{CreditCalculator, CurrentGuardianWeeklySubscription, ExtendedTerm, GuardianWeeklyHolidayStopConfig, HolidayCreditProduct, HolidayCreditUpdate, HolidayStop, OverallFailure, SalesforceHolidayWriteError, Subscription, ZuoraHolidayWriteError}
import com.gu.salesforce.holiday_stops.SalesforceHolidayStopRequestsDetail.{HolidayStopRequestsDetail, HolidayStopRequestsDetailChargeCode, HolidayStopRequestsDetailChargePrice, ProductName, StoppedPublicationDate, SubscriptionName}
import cats.implicits._
import com.gu.holiday_stops.ActionCalculator.{GuardianWeeklySuspensionConstants, suspensionConstantsByProduct}

object GuardianWeeklyHolidayStopProcess {
def processHolidayStops(
config: GuardianWeeklyHolidayStopConfig,
getHolidayStopRequestsFromSalesforce: (ProductName, LocalDate) => Either[OverallFailure, List[HolidayStopRequestsDetail]],
getSubscription: SubscriptionName => Either[ZuoraHolidayWriteError, Subscription],
updateSubscription: (Subscription, HolidayCreditUpdate) => Either[ZuoraHolidayWriteError, Unit],
writeHolidayStopsToSalesforce: List[HolidayStopResponse] => Either[SalesforceHolidayWriteError, Unit],
processDateOverride: Option[LocalDate]
): ProcessResult = {
getHolidayStopRequestsFromSalesforce(ProductName("Guardian Weekly"), calculateProcessDate(processDateOverride)) match {
case Left(overallFailure) =>
ProcessResult(overallFailure)

case Right(holidayStopRequestsFromSalesforce) =>
val holidayStops = holidayStopRequestsFromSalesforce.distinct.map(HolidayStop(_))
val alreadyActionedHolidayStops = holidayStopRequestsFromSalesforce.flatMap(_.Charge_Code__c).distinct
val allZuoraHolidayStopResponses =
holidayStops.map(
writeHolidayStopToZuora(
config.holidayCreditProduct,
config.productRatePlanIds,
config.nForNProductRatePlanIds,
getSubscription,
updateSubscription
)
)
val (failedZuoraResponses, successfulZuoraResponses) = allZuoraHolidayStopResponses.separate
val notAlreadyActionedHolidays = successfulZuoraResponses.filterNot(v => alreadyActionedHolidayStops.contains(v.chargeCode))
val salesforceExportResult = writeHolidayStopsToSalesforce(notAlreadyActionedHolidays)
ProcessResult(
holidayStops,
allZuoraHolidayStopResponses,
notAlreadyActionedHolidays,
OverallFailure(failedZuoraResponses, salesforceExportResult)
)
}
}

private def calculateProcessDate(processDateOverride: Option[LocalDate]) = {
processDateOverride.getOrElse(LocalDate.now.plusDays(GuardianWeeklySuspensionConstants.processorRunLeadTimeDays))
}

/**
* This is the main business logic for writing holiday stop to Zuora
*/
def writeHolidayStopToZuora(
holidayCreditProduct: HolidayCreditProduct,
guardianWeeklyProductRatePlanIds: List[String],
gwNforNProductRatePlanIds: List[String],
getSubscription: SubscriptionName => Either[ZuoraHolidayWriteError, Subscription],
updateSubscription: (Subscription, HolidayCreditUpdate) => Either[ZuoraHolidayWriteError, Unit]
)(stop: HolidayStop): Either[ZuoraHolidayWriteError, HolidayStopResponse] =
for {
subscription <- getSubscription(stop.subscriptionName)
_ <- if (subscription.autoRenew) Right(()) else Left(ZuoraHolidayWriteError("Cannot currently process non-auto-renewing subscription"))
currentGuardianWeeklySubscription <- CurrentGuardianWeeklySubscription(subscription, guardianWeeklyProductRatePlanIds, gwNforNProductRatePlanIds)
nextInvoiceStartDate = NextBillingPeriodStartDate(currentGuardianWeeklySubscription, stop.stoppedPublicationDate)
maybeExtendedTerm = ExtendedTerm(nextInvoiceStartDate, subscription)
holidayCredit <- CreditCalculator.guardianWeeklyCredit(guardianWeeklyProductRatePlanIds, gwNforNProductRatePlanIds, stop.stoppedPublicationDate)(subscription)
holidayCreditUpdate <- HolidayCreditUpdate(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(ZuoraHolidayWriteError("Failed to add charge to subscription"))
} yield {
HolidayStopResponse(
stop.requestId,
stop.subscriptionName,
stop.productName,
HolidayStopRequestsDetailChargeCode(addedCharge.number),
stop.estimatedCharge,
HolidayStopRequestsDetailChargePrice(addedCharge.price),
StoppedPublicationDate(addedCharge.HolidayStart__c.getOrElse(LocalDate.MIN))
)
}
}
Expand Up @@ -24,15 +24,15 @@ object Handler extends Lambda[Option[LocalDate], List[HolidayStopResponse]] {
Left(new RuntimeException(s"Config failure: $msg"))

case Right(config) =>
val result = HolidayStopProcess(config, processDateOverride, HttpURLConnectionBackend())
ProcessResult.log(result)
result.overallFailure match {
case Some(failure) =>
Left(new RuntimeException(failure.reason))

case None =>
val (_, successfulZuoraResponses) = result.holidayStopResults.separate
val results = HolidayStopProcess(config, processDateOverride, HttpURLConnectionBackend())
results.foreach(result => ProcessResult.log(result))
results.flatMap(_.overallFailure.toList) match {
case Nil =>
val (_, successfulZuoraResponses) = results.flatMap(_.holidayStopResults).separate
Right(successfulZuoraResponses)
case failures =>
Left(new RuntimeException(failures.map(_.reason).mkString("; ")))

}
}
}
Expand Down
Expand Up @@ -2,89 +2,34 @@ package com.gu.holidaystopprocessor

import java.time.LocalDate

import cats.implicits._
import com.gu.holiday_stops._
import com.gu.salesforce.holiday_stops.SalesforceHolidayStopRequestsDetail._
import com.softwaremill.sttp.{Id, SttpBackend}

object HolidayStopProcess {

def apply(config: Config, processDateOverride: Option[LocalDate], backend: SttpBackend[Id, Nothing]): ProcessResult =
def apply(config: Config, processDateOverride: Option[LocalDate], backend: SttpBackend[Id, Nothing]): List[ProcessResult] =
Zuora.accessTokenGetResponse(config.zuoraConfig, backend) match {
case Left(overallFailure) =>
ProcessResult(overallFailure)
List(ProcessResult(overallFailure))

case Right(zuoraAccessToken) =>
processHolidayStops(
config.holidayCreditProduct,
guardianWeeklyProductRatePlanIds = config.guardianWeeklyProductRatePlanIds,
gwNforNProductRatePlanIds = config.gwNforNProductRatePlanIds,
getHolidayStopRequestsFromSalesforce = Salesforce.holidayStopRequests(config.sfConfig, processDateOverride),
getSubscription = Zuora.subscriptionGetResponse(config, zuoraAccessToken, backend),
updateSubscription = Zuora.subscriptionUpdateResponse(config, zuoraAccessToken, backend),
writeHolidayStopsToSalesforce = Salesforce.holidayStopUpdateResponse(config.sfConfig)
List(
GuardianWeeklyHolidayStopProcess.processHolidayStops(
config = config.guardianWeeklyConfig,
getHolidayStopRequestsFromSalesforce = Salesforce.holidayStopRequests(config.sfConfig),
getSubscription = Zuora.subscriptionGetResponse(config, zuoraAccessToken, backend),
updateSubscription = Zuora.subscriptionUpdateResponse(config, zuoraAccessToken, backend),
writeHolidayStopsToSalesforce = Salesforce.holidayStopUpdateResponse(config.sfConfig),
processDateOverride
),
SundayVoucherHolidayStopProcessor.processHolidayStops(
config = config.sundayVoucherConfig,
getHolidayStopRequestsFromSalesforce = Salesforce.holidayStopRequests(config.sfConfig),
getSubscription = Zuora.subscriptionGetResponse(config, zuoraAccessToken, backend),
updateSubscription = Zuora.subscriptionUpdateResponse(config, zuoraAccessToken, backend),
writeHolidayStopsToSalesforce = Salesforce.holidayStopUpdateResponse(config.sfConfig),
processDateOverride
)
)
}

def processHolidayStops(
holidayCreditProduct: HolidayCreditProduct,
guardianWeeklyProductRatePlanIds: List[String],
gwNforNProductRatePlanIds: List[String],
getHolidayStopRequestsFromSalesforce: ProductName => Either[OverallFailure, List[HolidayStopRequestsDetail]],
getSubscription: SubscriptionName => Either[ZuoraHolidayWriteError, Subscription],
updateSubscription: (Subscription, HolidayCreditUpdate) => Either[ZuoraHolidayWriteError, Unit],
writeHolidayStopsToSalesforce: List[HolidayStopResponse] => Either[SalesforceHolidayWriteError, Unit]
): ProcessResult = {
getHolidayStopRequestsFromSalesforce(ProductName("Guardian Weekly")) match {
case Left(overallFailure) =>
ProcessResult(overallFailure)

case Right(holidayStopRequestsFromSalesforce) =>
val holidayStops = holidayStopRequestsFromSalesforce.distinct.map(HolidayStop(_))
val alreadyActionedHolidayStops = holidayStopRequestsFromSalesforce.flatMap(_.Charge_Code__c).distinct
val allZuoraHolidayStopResponses = holidayStops.map(writeHolidayStopToZuora(holidayCreditProduct, guardianWeeklyProductRatePlanIds, gwNforNProductRatePlanIds, getSubscription, updateSubscription))
val (failedZuoraResponses, successfulZuoraResponses) = allZuoraHolidayStopResponses.separate
val notAlreadyActionedHolidays = successfulZuoraResponses.filterNot(v => alreadyActionedHolidayStops.contains(v.chargeCode))
val salesforceExportResult = writeHolidayStopsToSalesforce(notAlreadyActionedHolidays)
ProcessResult(
holidayStops,
allZuoraHolidayStopResponses,
notAlreadyActionedHolidays,
OverallFailure(failedZuoraResponses, salesforceExportResult)
)
}
}

/**
* This is the main business logic for writing holiday stop to Zuora
*/
def writeHolidayStopToZuora(
holidayCreditProduct: HolidayCreditProduct,
guardianWeeklyProductRatePlanIds: List[String],
gwNforNProductRatePlanIds: List[String],
getSubscription: SubscriptionName => Either[ZuoraHolidayWriteError, Subscription],
updateSubscription: (Subscription, HolidayCreditUpdate) => Either[ZuoraHolidayWriteError, Unit]
)(stop: HolidayStop): Either[ZuoraHolidayWriteError, HolidayStopResponse] =
for {
subscription <- getSubscription(stop.subscriptionName)
_ <- if (subscription.autoRenew) Right(()) else Left(ZuoraHolidayWriteError("Cannot currently process non-auto-renewing subscription"))
currentGuardianWeeklySubscription <- CurrentGuardianWeeklySubscription(subscription, guardianWeeklyProductRatePlanIds, gwNforNProductRatePlanIds)
nextInvoiceStartDate = NextBillingPeriodStartDate(currentGuardianWeeklySubscription, stop.stoppedPublicationDate)
maybeExtendedTerm = ExtendedTerm(nextInvoiceStartDate, subscription)
holidayCredit <- CreditCalculator.guardianWeeklyCredit(guardianWeeklyProductRatePlanIds, gwNforNProductRatePlanIds, stop.stoppedPublicationDate)(subscription)
holidayCreditUpdate <- HolidayCreditUpdate(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(ZuoraHolidayWriteError("Failed to add charge to subscription"))
} yield {
HolidayStopResponse(
stop.requestId,
stop.subscriptionName,
stop.productName,
HolidayStopRequestsDetailChargeCode(addedCharge.number),
stop.estimatedCharge,
HolidayStopRequestsDetailChargePrice(addedCharge.price),
StoppedPublicationDate(addedCharge.HolidayStart__c.getOrElse(LocalDate.MIN))
)
}
}
Expand Up @@ -9,25 +9,19 @@ import com.gu.salesforce.holiday_stops.SalesforceHolidayStopRequestsDetail
import com.gu.salesforce.holiday_stops.SalesforceHolidayStopRequestsDetail._
import com.gu.util.resthttp.JsonHttp
import scalaz.{-\/, \/-}
import com.gu.holiday_stops.ActionCalculator.suspensionConstantsByProduct
import com.gu.holiday_stops.{OverallFailure, SalesforceHolidayWriteError}

object Salesforce {

def holidayStopRequests(sfCredentials: SFAuthConfig, processDateOverride: Option[LocalDate])(productNamePrefix: ProductName): Either[OverallFailure, List[HolidayStopRequestsDetail]] = {
val processDate = LocalDate.now.plusDays(
suspensionConstantsByProduct(productNamePrefix).processorRunLeadTimeDays
)

def holidayStopRequests(sfCredentials: SFAuthConfig)(productNamePrefix: ProductName, processDate: LocalDate): Either[OverallFailure, List[HolidayStopRequestsDetail]] = {
SalesforceClient(RawEffects.response, sfCredentials).value.flatMap { sfAuth =>
val sfGet = sfAuth.wrapWith(JsonHttp.getWithParams)
val fetchOp = SalesforceHolidayStopRequestsDetail.LookupPendingByProductNamePrefixAndDate(sfGet)
fetchOp(productNamePrefix, processDateOverride.getOrElse(processDate))
fetchOp(productNamePrefix, processDate)
}.toDisjunction match {
case -\/(failure) => Left(OverallFailure(failure.toString))
case \/-(details) => Right(details)
}

}

def holidayStopUpdateResponse(sfCredentials: SFAuthConfig)(responses: List[HolidayStopResponse]): Either[SalesforceHolidayWriteError, Unit] =
Expand Down
Expand Up @@ -16,12 +16,12 @@ object StandaloneApp extends App {
case Right(config) =>
val processResult = HolidayStopProcess(config, stopDate, HttpURLConnectionBackend())

println(processResult.holidayStopsToApply.size)
println(processResult.flatMap(_.holidayStopsToApply).size)

processResult.overallFailure foreach { failure =>
processResult.flatMap(_.overallFailure) foreach { failure =>
println(s"Overall failure: ${failure.reason}")
}
processResult.holidayStopResults foreach {
processResult.flatMap(_.holidayStopResults) foreach {
case Left(failure) => println(s"Failed: ${failure.reason}")
case Right(response) => println(s"Success: $response")
}
Expand Down
@@ -0,0 +1,18 @@
package com.gu.holidaystopprocessor

import java.time.LocalDate

import com.gu.holiday_stops._
import com.gu.salesforce.holiday_stops.SalesforceHolidayStopRequestsDetail.{HolidayStopRequestsDetail, ProductName, SubscriptionName}

object SundayVoucherHolidayStopProcessor {
def processHolidayStops(
config: SundayVoucherHolidayStopConfig,
getHolidayStopRequestsFromSalesforce: (ProductName, LocalDate) => Either[OverallFailure, List[HolidayStopRequestsDetail]],
getSubscription: SubscriptionName => Either[ZuoraHolidayWriteError, Subscription],
updateSubscription: (Subscription, HolidayCreditUpdate) => Either[ZuoraHolidayWriteError, Unit],
writeHolidayStopsToSalesforce: List[HolidayStopResponse] => Either[SalesforceHolidayWriteError, Unit],
processDateOverride: Option[LocalDate]
): ProcessResult =
ProcessResult(Nil, Nil, Nil, None)
}

0 comments on commit 36262f7

Please sign in to comment.