All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gu.memsub.subsv2.reads.ChargeListReads.scala Maven / Gradle / Ivy

There is a newer version: 0.605
Show newest version
package com.gu.memsub.subsv2.reads
import com.gu.memsub.Benefit._
import com.gu.memsub.BillingPeriod._
import com.gu.memsub.Subscription.{ProductId, ProductRatePlanChargeId}
import com.gu.memsub._
import com.gu.memsub.subsv2._
import com.gu.memsub.subsv2.reads.ChargeListReads.PlanChargeMap
import com.gu.memsub.subsv2.reads.CommonReads.{FailureAggregatingOrElse, TraceableValidation}

import scala.reflect.ClassTag
import scalaz._
import scalaz.syntax.applicative._
import scalaz.syntax.nel._
import scalaz.syntax.std.boolean._
import scalaz.syntax.std.option._
import scalaz.Validation.FlatMap._

/**
  * Try to convert a single ZuoraCharge into some type A
  */
trait ChargeReads[A] {
  def read(cat: PlanChargeMap, charge: ZuoraCharge): ValidationNel[String, A]

  // only read if the result is the given type
  def filter[B <: A : ClassTag]: ChargeReads[B] = {
    val requiredClass = implicitly[ClassTag[B]].runtimeClass
    new ChargeReads[B] {
      override def read(cat: PlanChargeMap, charge: ZuoraCharge): ValidationNel[String, B] =
        ChargeReads.this.read(cat, charge).flatMap {
          case b: B if requiredClass.isInstance(b) => Success(b)
          case actual => Failure(s"expected $requiredClass but was $actual").toValidationNel
        }
    }
  }
}

/**
  * Try to convert a list of Zuora charges into some type A
  */
trait ChargeListReads[A] {
  def read(cat: PlanChargeMap, charges: List[ZuoraCharge]): ValidationNel[String, A]
}


object ChargeListReads {

  type PlanChargeMap = Map[ProductRatePlanChargeId, Benefit]

  def pure[A](a: A) = new ChargeListReads[A] {
    override def read(cat: PlanChargeMap, charges: List[ZuoraCharge]): ValidationNel[String, A] =
      Validation.success(a)
  }

  case class ProductIds(
    weeklyZoneA: ProductId,
    weeklyZoneB: ProductId,
    weeklyZoneC: ProductId,
    weeklyDomestic: ProductId,
    weeklyRestOfWorld: ProductId,
    friend: ProductId,
    supporter: ProductId,
    partner: ProductId,
    patron: ProductId,
    staff: ProductId,
    digipack: ProductId,
    voucher: ProductId,
    digitalVoucher: ProductId,
    delivery: ProductId,
    contributor: ProductId
  )

  // shorthand syntax for creating new ChargeListReads
  def apply[A](f: (PlanChargeMap, List[ZuoraCharge]) => ValidationNel[String, A]) = new ChargeListReads[A] {
    override def read(cat: PlanChargeMap, charges: List[ZuoraCharge]): ValidationNel[String, A] = f(cat, charges)
  }

  implicit def readAnyProduct[P <: Benefit : ClassTag]: ChargeReads[P] = new ChargeReads[Benefit] {
    def read(cat: PlanChargeMap, charge: ZuoraCharge): ValidationNel[String, Benefit] =
      cat.get(charge.productRatePlanChargeId).toSuccess(NonEmptyList(s"Could not find product ${charge.name} ${charge.productRatePlanChargeId} in catalog"))
  }.filter[P]

  implicit def anyBpReads[B <: BillingPeriod : ClassTag]: ChargeReads[B] = new ChargeReads[BillingPeriod] {

    def read(cat: PlanChargeMap, charge: ZuoraCharge): ValidationNel[String, BillingPeriod] = {

      ((charge.endDateCondition, charge.billingPeriod) match {
        case (FixedPeriod, Some(ZSpecificWeeks))
          if charge.specificBillingPeriod.exists(numberOfWeeks => numberOfWeeks == 6 || numberOfWeeks == 7) &&
            charge.upToPeriods.contains(1) &&
            charge.upToPeriodsType.contains(BillingPeriods) =>
          Success(SixWeeks)
        case (FixedPeriod, Some(zPeriod))
          if charge.upToPeriods.contains(1) &&
            charge.upToPeriodsType.contains(BillingPeriods) =>
          zPeriod match {
            case ZYear => Success(OneYear)
            case ZQuarter => Success(ThreeMonths)
            case ZTwoYears => Success(TwoYears)
            case ZThreeYears => Success(ThreeYears)
            case ZSemiAnnual => Success(SixMonths)
            case _ => Failure(s"zuora fixed period was $zPeriod")
          }
        case (SubscriptionEnd, Some(zPeriod)) =>
          zPeriod match {
            case ZMonth => Success(Month)
            case ZQuarter => Success(Quarter)
            case ZYear => Success(Year)
            case ZSemiAnnual => Success(SixMonthsRecurring)
            case _ => Failure(s"zuora recurring period was $zPeriod")
          }
        case _ =>
          Failure(s"period =${charge.billingPeriod} specificBillingPeriod=${charge.specificBillingPeriod} uptoPeriodsType=${charge.upToPeriodsType}, uptoPeriods=${charge.upToPeriods}")
      }).toValidationNel[String, BillingPeriod].withTrace("anyBpReads")


    }
  }.filter[B]

  implicit def readPaidCharge[P <: Benefit, BP <: BillingPeriod](implicit product: ChargeReads[P], bp: ChargeReads[BP]): ChargeListReads[PaidCharge[P, BP]] = new ChargeListReads[PaidCharge[P, BP]] {
    def read(cat: PlanChargeMap, charges: List[ZuoraCharge]): ValidationNel[String, PaidCharge[P, BP]] = charges match {
      case charge :: Nil => (product.read(cat, charge) |@| bp.read(cat, charge) |@|
        charge.pricing.prices.exists(_.amount != 0).option(charge.pricing).toSuccess(NonEmptyList("Could not read paid charge: Charge is free")))
        .apply({ case(p, b, pricing) => PaidCharge(p, b, pricing, charge.productRatePlanChargeId, charge.id) })
      case charge :: others => Validation.failureNel(s"Too many charges! I got $charge and $others")
      case Nil => Validation.failureNel(s"No charges found!")
    }
  }

  implicit def readFreeCharge[P <: Benefit](implicit product: ChargeReads[P]): ChargeListReads[FreeCharge[P]] = new ChargeListReads[FreeCharge[P]] {
    def read(cat: PlanChargeMap, charges: List[ZuoraCharge]): ValidationNel[String, FreeCharge[P]] = charges match {
      case charge :: Nil => (product.read(cat, charge) |@| charge.pricing.prices.forall(_.amount == 0).option(charge.pricing)
        .toSuccess(NonEmptyList("Could not read free charge: Charge is paid"))).apply({ case (p, _) => FreeCharge(p, charge.pricing.currencies) })
      case charge :: others => Validation.failureNel(s"Too many charges! I got $charge and $others")
      case Nil => Validation.failureNel(s"No charges found!")
    }
  }

  implicit def readPaidChargeList: ChargeListReads[PaidChargeList] = new ChargeListReads[PaidChargeList] {
    def read(cat: PlanChargeMap, charges: List[ZuoraCharge]): ValidationNel[String, PaidChargeList] = {
      readPaperChargeList.read(cat, charges) orElse2
       readPaidCharge[Benefit, BillingPeriod](readAnyProduct, anyBpReads).read(cat, charges)
    }.withTrace("readPaidChargeList")
  }

  implicit def readFreeChargeList: ChargeListReads[FreeChargeList] = new ChargeListReads[FreeChargeList] {
    def read(cat: PlanChargeMap, charges: List[ZuoraCharge]): ValidationNel[String, FreeChargeList] = {
      readFreeCharge[Benefit](readAnyProduct).read(cat, charges)
    }
  }

  implicit def readChargeList: ChargeListReads[ChargeList] = new ChargeListReads[ChargeList] {
    override def read(cat: PlanChargeMap, charges: List[ZuoraCharge]) = {
      readPaidChargeList.read(cat, charges) orElse2
      readFreeChargeList.read(cat, charges)
    }.withTrace("readChargeList")
  }

  implicit def readPaperChargeList: ChargeListReads[PaperCharges] = new ChargeListReads[PaperCharges] {

    def findDigipack(chargeMap: List[(Benefit, PricingSummary)]): ValidationNel[String, Option[PricingSummary]] =
      chargeMap.collect { case (Digipack, p) => (Digipack, p) } match {
        case Nil => Validation.success(None)
        case n :: Nil => Validation.success(Some(n._2))
        case n :: ns => Validation.failureNel("Too many digipacks")
      }

    def getDays(chargeMap: List[(Benefit, PricingSummary)]): ValidationNel[String, Map[PaperDay, PricingSummary]] = {
     val foundDays = chargeMap.collect({ case(d: PaperDay, p) => (d, p) })
      Validation.success(foundDays.toMap)
        .ensure("There are duplicate days".wrapNel)(_.size == foundDays.size)
        .ensure("No days found".wrapNel)(_.nonEmpty)
    }

    override def read(cat: PlanChargeMap, charges: List[ZuoraCharge]): ValidationNel[String, PaperCharges] = {
      val chargeMap = charges.flatMap(c => cat.get(c.productRatePlanChargeId).map(_ -> c.pricing))
      (getDays(chargeMap) |@| findDigipack(chargeMap))(PaperCharges).withTrace("readPaperChargeList")
    }
  }

  implicit def readSingle[B <: Benefit : ChargeReads]: ChargeListReads[ChargeList with SingleBenefit[B]] =
    new ChargeListReads[ChargeList with SingleBenefit[B]] {
    def read(cat: PlanChargeMap, charges: List[ZuoraCharge]): ValidationNel[String, ChargeList with SingleBenefit[B]] = {
      readPaidCharge[B, BillingPeriod].read(cat, charges) orElse2
      readFreeCharge[B].read(cat, charges)
    }.withTrace("readSingle")
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy