+ //+ Utility methods
+ def rcDebugInfo(rcEvent: ResourceEvent) = {
+ rcEvent.toDebugString(false)
+ }
+ //- Utility methods
+
+
+ def walletEntriesForResourceEvent(startingUserState: UserState,
+ userStateWorker: UserStateWorker,
+ currentResourceEvent: ResourceEvent,
+ policyStore: PolicyStore,
+ stateChangeReason: UserStateChangeReason,
+ billingMonthInfo: BillingMonthInfo,
+ clogM: Maybe[ContextualLogger] = NoVal): List[NewWalletEntry] = {
+
+ val clog = ContextualLogger.fromOther(clogM, logger, "walletEntriesForResourceEvent(%s)", currentResourceEvent.id)
+
+ val newWalletEntriesBuffer = ListBuffer[NewWalletEntry]()
+ var _workingUserState: UserState = startingUserState
+ val theResource = currentResourceEvent.safeResource
+ val theInstanceId = currentResourceEvent.safeInstanceId
+ val theValue = currentResourceEvent.value
+
+ val accounting = userStateWorker.accounting
+ val defaultResourcesMap = userStateWorker.resourcesMap
+
+ val currentResourceEventDebugInfo = rcDebugInfo(currentResourceEvent)
+ clog.begin(currentResourceEventDebugInfo)
+
+ userStateWorker.debugTheMaps(clog)(rcDebugInfo)
+
+ // Ignore the event if it is not billable (but still record it in the "previous" stuff).
+ // But to make this decision, first we need the resource definition (and its cost policy).
+ val dslResourceOpt = defaultResourcesMap.findResource(theResource)
+
+ dslResourceOpt match {
+ // We have a resource (and thus a cost policy)
+ case Some(dslResource) ⇒
+ val costPolicy = dslResource.costPolicy
+ clog.debug("Cost policy %s for %s", costPolicy, dslResource)
+ val isBillable = costPolicy.isBillableEventBasedOnValue(theValue)
+ if(!isBillable) {
+ // The resource event is not billable
+ clog.debug("Ignoring not billable event %s", currentResourceEventDebugInfo)
+ } else {
+ // The resource event is billable
+ // Find the previous event.
+ // This is (potentially) needed to calculate new credit amount and new resource instance amount
+ val previousResourceEventM = userStateWorker.findAndRemovePreviousResourceEvent(theResource, theInstanceId)
+ clog.debug("PreviousM %s", previousResourceEventM.map(rcDebugInfo(_)))
+
+ val havePreviousResourceEvent = previousResourceEventM.isJust
+ val needPreviousResourceEvent = costPolicy.needsPreviousEventForCreditAndAmountCalculation
+ if(needPreviousResourceEvent && !havePreviousResourceEvent) {
+ // This must be the first resource event of its kind, ever.
+ // TODO: We should normally check the DB to verify the claim (?)
+ clog.info("Ignoring first event of its kind %s", currentResourceEventDebugInfo)
+ userStateWorker.updateIgnored(currentResourceEvent)
+ } else {
+ val defaultInitialAmount = costPolicy.getResourceInstanceInitialAmount
+ val oldAmount = _workingUserState.getResourceInstanceAmount(theResource, theInstanceId, defaultInitialAmount)
+ val oldCredits = _workingUserState.creditsSnapshot.creditAmount
+
+ // A. Compute new resource instance accumulating amount
+ val newAmount = costPolicy.computeNewAccumulatingAmount(oldAmount, theValue)
+
+ clog.debug("theValue = %s, oldAmount = %s, newAmount = %s, oldCredits = %s", theValue, oldAmount, newAmount, oldCredits)
+
+ // B. Compute new wallet entries
+ clog.debug("agreementsSnapshot = %s", _workingUserState.agreementsSnapshot)
+ val alltimeAgreements = _workingUserState.agreementsSnapshot.agreementsByTimeslot
+
+ // clog.debug("Computing full chargeslots")
+ val fullChargeslotsM = accounting.computeFullChargeslots(
+ previousResourceEventM,
+ currentResourceEvent,
+ oldCredits,
+ oldAmount,
+ newAmount,
+ dslResource,
+ defaultResourcesMap,
+ alltimeAgreements,
+ SimpleCostPolicyAlgorithmCompiler,
+ policyStore,
+ Just(clog)
+ )
+
+ // We have the chargeslots, let's associate them with the current event
+ fullChargeslotsM match {
+ case Just((referenceTimeslot, fullChargeslots)) ⇒
+ if(fullChargeslots.length == 0) {
+ // At least one chargeslot is required.
+ throw new Exception("No chargeslots computed for resource event %s".format(currentResourceEvent.id))
+ }
+ clog.debugSeq("fullChargeslots", fullChargeslots, 0)
+
+ // C. Compute new credit amount (based on the charge slots)
+ val newCreditsDiff = fullChargeslots.map(_.computedCredits.get).sum
+ val newCredits = oldCredits - newCreditsDiff
+
+ if(stateChangeReason.shouldStoreCalculatedWalletEntries) {
+ val newWalletEntry = NewWalletEntry(
+ userStateWorker.userId,
+ newCreditsDiff,
+ oldCredits,
+ newCredits,
+ TimeHelpers.nowMillis,
+ referenceTimeslot,
+ billingMonthInfo.year,
+ billingMonthInfo.month,
+ if(havePreviousResourceEvent)
+ List(currentResourceEvent, justForSure(previousResourceEventM).get)
+ else
+ List(currentResourceEvent),
+ fullChargeslots,
+ dslResource
+ )
+ clog.debug("New %s", newWalletEntry)
+
+ newWalletEntriesBuffer += newWalletEntry
+ } else {
+ clog.debug("newCreditsDiff = %s, newCredits = %s", newCreditsDiff, newCredits)
+ }
+
+ _workingUserState = _workingUserState.copy(
+ creditsSnapshot = CreditSnapshot(newCredits, TimeHelpers.nowMillis),
+ stateChangeCounter = _workingUserState.stateChangeCounter + 1,
+ totalEventsProcessedCounter = _workingUserState.totalEventsProcessedCounter + 1
+ )
+
+ case NoVal ⇒
+ // At least one chargeslot is required.
+ throw new Exception("No chargeslots computed")
+
+ case failed@Failed(e, m) ⇒
+ throw new Exception(m, e)
+ }
+ }
+ }
+
+ // After processing, all event, billable or not update the previous state
+ userStateWorker.updatePrevious(currentResourceEvent)
+
+ // We do not have a resource (and no cost policy)
+ case None ⇒
+ // Now, this is a matter of politics: what do we do if no policy was found?
+ clog.warn("Unknown resource for %s", currentResourceEventDebugInfo)
+ } // dslResourceOpt match
+
+ clog.end(currentResourceEventDebugInfo)
+ newWalletEntriesBuffer.toList
+ }
+